def create_generator(self, bdb, generator_id, schema, **kwargs): # XXX Do something with the schema. insert_column_sql = ''' INSERT INTO bayesdb_nig_normal_column (population_id, generator_id, colno, count, sum, sumsq) VALUES (:population_id, :generator_id, :colno, :count, :sum, :sumsq) ''' population_id = core.bayesdb_generator_population(bdb, generator_id) table = core.bayesdb_population_table(bdb, population_id) for colno in core.bayesdb_variable_numbers(bdb, population_id, None): column_name = core.bayesdb_variable_name(bdb, population_id, generator_id, colno) stattype = core.bayesdb_variable_stattype(bdb, population_id, generator_id, colno) if not stattype == 'numerical': raise BQLError( bdb, 'NIG-Normal only supports' ' numerical columns, but %s is %s' % (repr(column_name), repr(stattype))) (count, xsum, sumsq) = data_suff_stats(bdb, table, column_name) bdb.sql_execute( insert_column_sql, { 'population_id': population_id, 'generator_id': generator_id, 'colno': colno, 'count': count, 'sum': xsum, 'sumsq': sumsq, }) # XXX Make the schema a little more flexible. if schema == [[]]: return for clause in schema: if not (len(clause) == 3 and \ isinstance(clause[0], str) and \ clause[1] == 'deviation' and \ isinstance(clause[2], list) and \ len(clause[2]) == 1 and \ isinstance(clause[2][0], str)): raise BQLError(bdb, 'Invalid nig_normal clause: %r' % (clause, )) dev_var = clause[0] obs_var = clause[2][0] if not core.bayesdb_has_variable(bdb, population_id, None, obs_var): raise BQLError(bdb, 'No such variable: %r' % (obs_var, )) obs_colno = core.bayesdb_variable_number(bdb, population_id, None, obs_var) dev_colno = core.bayesdb_add_latent(bdb, population_id, generator_id, dev_var, 'numerical') bdb.sql_execute( ''' INSERT INTO bayesdb_nig_normal_deviation (population_id, generator_id, deviation_colno, observed_colno) VALUES (?, ?, ?, ?) ''', (population_id, generator_id, dev_colno, obs_colno))
def create_generator(self, bdb, generator_id, schema, **kwargs): # XXX Do something with the schema. insert_column_sql = ''' INSERT INTO bayesdb_nig_normal_column (population_id, generator_id, colno, count, sum, sumsq) VALUES (:population_id, :generator_id, :colno, :count, :sum, :sumsq) ''' population_id = core.bayesdb_generator_population(bdb, generator_id) table = core.bayesdb_population_table(bdb, population_id) for colno in core.bayesdb_variable_numbers(bdb, population_id, None): column_name = core.bayesdb_variable_name( bdb, population_id, generator_id, colno) stattype = core.bayesdb_variable_stattype( bdb, population_id, generator_id, colno) if not stattype == 'numerical': raise BQLError(bdb, 'NIG-Normal only supports' ' numerical columns, but %s is %s' % (repr(column_name), repr(stattype))) (count, xsum, sumsq) = data_suff_stats(bdb, table, column_name) bdb.sql_execute(insert_column_sql, { 'population_id': population_id, 'generator_id': generator_id, 'colno': colno, 'count': count, 'sum': xsum, 'sumsq': sumsq, }) # XXX Make the schema a little more flexible. if schema == [[]]: return for clause in schema: if not (len(clause) == 3 and \ isinstance(clause[0], str) and \ clause[1] == 'deviation' and \ isinstance(clause[2], list) and \ len(clause[2]) == 1 and \ isinstance(clause[2][0], str)): raise BQLError(bdb, 'Invalid nig_normal clause: %r' % (clause,)) dev_var = clause[0] obs_var = clause[2][0] if not core.bayesdb_has_variable(bdb, population_id, None, obs_var): raise BQLError(bdb, 'No such variable: %r' % (obs_var,)) obs_colno = core.bayesdb_variable_number(bdb, population_id, None, obs_var) dev_colno = core.bayesdb_add_latent(bdb, population_id, generator_id, dev_var, 'numerical') bdb.sql_execute(''' INSERT INTO bayesdb_nig_normal_deviation (population_id, generator_id, deviation_colno, observed_colno) VALUES (?, ?, ?, ?) ''', (population_id, generator_id, dev_colno, obs_colno))
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
def _create_schema(bdb, generator_id, schema_ast, **kwargs): # Get some parameters. population_id = core.bayesdb_generator_population(bdb, generator_id) table = core.bayesdb_population_table(bdb, population_id) # State. variables = [] variable_dist = {} latents = {} cgpm_composition = [] modelled = set() default_modelled = set() subsample = None deferred_input = defaultdict(lambda: []) deferred_output = dict() # Error-reporting state. duplicate = set() unknown = set() needed = set() existing_latent = set() must_exist = [] unknown_stattype = {} # XXX Convert all Foreign.exposed lists to Latent clauses. # Retrieve Foreign clauses with exposed variables. foreign_clauses = [ c for c in schema_ast if isinstance(c, cgpm_schema.parse.Foreign) and len(c.exposed) > 0 ] # Add the exposed variables to Foreign.outputs # Note that this assumes if there are K exposed variables, then they are # necessarily the last K outputs of the fc.outputs. for fc in foreign_clauses: fc.outputs.extend([e[0] for e in fc.exposed]) # Convert exposed entries into Latent clauses. latent_vars = list( itertools.chain.from_iterable(c.exposed for c in foreign_clauses)) latent_clauses = [cgpm_schema.parse.Latent(v, s) for (v, s) in latent_vars] # Append the Latent clauses to the ast. schema_ast.extend(latent_clauses) # XXX Convert the baseline to a Foreign clause. # Currently the baselines do not accept a schema, and will fail if # `schema_ast` has any entries. baseline = kwargs.get('baseline', None) if baseline is not None and casefold(baseline.name) != 'crosscat': if schema_ast: raise BQLError( bdb, 'Cannot accept schema with baseline: %s.' % schema_ast) # Retrieve all variable names in the population outputs = core.bayesdb_variable_names(bdb, population_id, None) # Convert the LITERAL namedtuples to their raw values. ps, vs = zip(*baseline.params) vs_new = [v.value for v in vs] params = zip(ps, vs_new) # Create the clause. clause = cgpm_schema.parse.Foreign(outputs, [], [], baseline.name, params) # And add append it to the schema_ast. schema_ast.append(clause) # Process each clause one by one. for clause in schema_ast: if isinstance(clause, cgpm_schema.parse.Basic): # Basic Crosscat component model: one variable to be put # into Crosscat views. var = clause.var dist = clause.dist params = dict(clause.params) # XXX error checking # Reject if the variable does not exist. if not core.bayesdb_has_variable(bdb, population_id, None, var): unknown.add(var) continue # Reject if the variable has already been modelled. if var in modelled: duplicate.add(var) continue # Reject if the variable is latent. if core.bayesdb_has_latent(bdb, population_id, var): existing_latent.add(var) continue # Get the column number. colno = core.bayesdb_variable_number(bdb, population_id, None, var) assert 0 <= colno # Add it to the list and mark it modelled by default. stattype = core.bayesdb_variable_stattype(bdb, population_id, colno) variables.append([var, stattype, dist, params]) assert var not in variable_dist variable_dist[var] = (stattype, dist, params) modelled.add(var) default_modelled.add(var) elif isinstance(clause, cgpm_schema.parse.Latent): var = clause.name stattype = clause.stattype # Reject if the variable has already been modelled by the # default model. if var in default_modelled: duplicate.add(var) continue # Reject if the variable even *exists* in the population # at all yet. if core.bayesdb_has_variable(bdb, population_id, None, var): duplicate.add(var) continue # Reject if the variable is already latent, from another # generator. if core.bayesdb_has_latent(bdb, population_id, var): existing_latent.add(var) continue # Reject if we've already processed it. if var in latents: duplicate.add(var) continue # Add it to the set of latent variables. latents[var] = stattype elif isinstance(clause, cgpm_schema.parse.Foreign): # Foreign model: some set of output variables is to be # modelled by foreign logic, possibly conditional on some # set of input variables. # # Gather up the state for a cgpm_composition record, which # we may have to do incrementally because it must refer to # the distribution types of variables we may not have # seen. name = clause.name outputs = clause.outputs inputs = clause.inputs output_stattypes = [] output_statargs = [] input_stattypes = [] input_statargs = [] distargs = { 'inputs': { 'stattypes': input_stattypes, 'statargs': input_statargs }, 'outputs': { 'stattypes': output_stattypes, 'statargs': output_statargs, } } kwds = {'distargs': distargs} kwds.update(clause.params) # First make sure all the output variables exist and have # not yet been modelled. for var in outputs: must_exist.append(var) if var in modelled: duplicate.add(var) continue modelled.add(var) # Add the output statistical type and its parameters. i = len(output_stattypes) assert i == len(output_statargs) output_stattypes.append(None) output_statargs.append(None) deferred_output[var] = (output_stattypes, output_statargs, i) # Next make sure all the input variables exist, mark them # needed, and record where to put their distribution type # and parameters. for var in inputs: must_exist.append(var) needed.add(var) i = len(input_stattypes) assert i == len(input_statargs) input_stattypes.append(None) input_statargs.append(None) deferred_input[var].append( (input_stattypes, input_statargs, i)) # Finally, add a cgpm_composition record. cgpm_composition.append({ 'name': name, 'inputs': inputs, 'outputs': outputs, 'kwds': kwds, }) elif isinstance(clause, cgpm_schema.parse.Subsample): if subsample is not None: raise BQLError(bdb, 'Duplicate subsample: %r' % (clause.n, )) subsample = clause.n else: raise BQLError(bdb, 'Unknown clause: %r' % (clause, )) # Make sure all the outputs and inputs exist, either in the # population or as latents in this generator. for var in must_exist: if core.bayesdb_has_variable(bdb, population_id, None, var): continue if var in latents: continue unknown.add(var) # Raise an exception if there were duplicates or unknown # variables. if duplicate: raise BQLError(bdb, 'Duplicate model variables: %r' % (sorted(duplicate), )) if existing_latent: raise BQLError( bdb, 'Latent variables already defined: %r' % (sorted(existing_latent), )) if unknown: raise BQLError(bdb, 'Unknown model variables: %r' % (sorted(unknown), )) def default_dist(var, stattype): stattype = casefold(stattype) if stattype not in _DEFAULT_DIST: if var in unknown_stattype: assert unknown_stattype[var] == stattype else: unknown_stattype[var] = stattype return None dist, params = _DEFAULT_DIST[stattype](bdb, generator_id, var) return dist, params # Use the default distribution for any variables that remain to be # modelled, excluding any that are latent or that have statistical # types we don't know about. for var in core.bayesdb_variable_names(bdb, population_id, None): if var in modelled: continue colno = core.bayesdb_variable_number(bdb, population_id, None, var) assert 0 <= colno stattype = core.bayesdb_variable_stattype(bdb, population_id, colno) distparams = default_dist(var, stattype) if distparams is None: continue dist, params = distparams variables.append([var, stattype, dist, params]) assert var not in variable_dist variable_dist[var] = (stattype, dist, params) modelled.add(var) # Fill in the deferred_input statistical type assignments. for var in sorted(deferred_input.iterkeys()): # Check whether the variable is modelled. If not, skip -- we # will fail later because this variable is guaranteed to also # be in needed. if var not in modelled: assert var in needed continue # Determine (possibly fictitious) distribution and parameters. if var in default_modelled: # Manifest variable modelled by default Crosscat model. assert var in variable_dist stattype, dist, params = variable_dist[var] else: # Modelled by a foreign model. Assign a fictitious # default distribution because the 27B/6 of CGPM requires # this. if var in latents: # Latent variable modelled by a foreign model. Use # the statistical type specified for it. stattype = latents[var] else: # Manifest variable modelled by a foreign model. Use # the statistical type in the population. assert core.bayesdb_has_variable(bdb, population_id, None, var) colno = core.bayesdb_variable_number(bdb, population_id, None, var) stattype = core.bayesdb_variable_stattype( bdb, population_id, colno) distparams = default_dist(var, stattype) if distparams is None: continue dist, params = distparams # Assign the distribution and parameters. for cctypes, ccargs, i in deferred_input[var]: assert cctypes[i] is None assert ccargs[i] is None cctypes[i] = dist ccargs[i] = params # Fill in the deferred_output statistical type assignments. The need to be # in the form NUMERICAL or CATEGORICAL. for var in deferred_output: if var in latents: # Latent variable modelled by a foreign model. Use # the statistical type specified for it. var_stattype = casefold(latents[var]) if var_stattype not in _DEFAULT_DIST: if var in unknown_stattype: assert unknown_stattype[var] == var_stattype else: unknown_stattype[var] = var_stattype # XXX Cannot specify statargs for a latent variable. Trying to using # default_dist might lookup the counts for unique values of the # categorical in the base table causing a failure. var_statargs = {} else: # Manifest variable modelled by a foreign model. Use # the statistical type and arguments from the population. assert core.bayesdb_has_variable(bdb, population_id, None, var) colno = core.bayesdb_variable_number(bdb, population_id, None, var) var_stattype = core.bayesdb_variable_stattype( bdb, population_id, colno) distparams = default_dist(var, var_stattype) if distparams is None: continue _, var_statargs = distparams stattypes, statargs, i = deferred_output[var] assert stattypes[i] is None assert statargs[i] is None stattypes[i] = var_stattype statargs[i] = var_statargs if unknown_stattype: raise BQLError( bdb, 'Unknown statistical types for variables: %r' % (sorted(unknown_stattype.iteritems(), ))) # If there remain any variables that we needed to model, because # others are conditional on them, fail. needed -= modelled if needed: raise BQLError(bdb, 'Unmodellable variables: %r' % (needed, )) # Finally, create a CGPM schema. return { 'variables': variables, 'cgpm_composition': cgpm_composition, 'subsample': subsample, 'latents': latents, }
def retrieve_analyze_variables(ast): # Transition all variables by default. variables = None # Exactly 1 VARIABLES or SKIP clause supported for simplicity. seen_variables, seen_skip, seen_optimized = False, False, False for clause in ast: # Transition user specified variables only. if isinstance(clause, cgpm_analyze.parse.Variables): if seen_variables or seen_skip: raise BQLError( bdb, 'Only 1 VARIABLES or SKIP clause allowed in ANALYZE' ) seen_variables = True included = set() unknown = set() for var in clause.vars: if not core.bayesdb_has_variable( bdb, population_id, generator_id, var): unknown.add(var) included.add(var) if unknown: raise BQLError( bdb, 'Unknown variables in ANALYZE: %r' % (sorted(unknown), )) variables = sorted(included) # Transition all variables except user specified skip. elif isinstance(clause, cgpm_analyze.parse.Skip): if seen_variables or seen_skip: raise BQLError( bdb, 'Only 1 VARIABLES or SKIP clause allowed in ANALYZE' ) seen_skip = True excluded = set() unknown = set() for var in clause.vars: if not core.bayesdb_has_variable( bdb, population_id, generator_id, var): unknown.add(var) excluded.add(var) if unknown: raise BQLError( bdb, 'Unknown variables in ANALYZE: %r' % (sorted(unknown), )) all_vars = core.bayesdb_variable_names( bdb, population_id, generator_id) variables = sorted(set(all_vars) - excluded) elif isinstance(clause, cgpm_analyze.parse.Optimized): seen_optimized = True # Unknown/impossible clause. else: raise ValueError('Unknown clause in ANALYZE: %s.' % ast) if variables is None: variables = core.bayesdb_variable_names( bdb, population_id, generator_id) varnos = [ core.bayesdb_variable_number(bdb, population_id, generator_id, v) for v in variables ] # TODO Perform error checking if the OPTIMIZED clause is used. # In particular, the variables in OPTIMIZED must correspond # EXACTLY to the variables that are modeled by the CrossCat # baseline. Avoided this check for now since the nature of a # variable is not stored in the bdb. For now, just check the # user did not include a VARIABLES clause. if seen_optimized: if seen_variables: raise BQLError(bdb, 'OPTIMIZED incompatible with VARIABLES') # TODO Check if varnos are exactly the CrossCat variables. # raise BQLError(bdb, # 'The OPTIMIZED phrase in ANALYZE must target all the ' # 'variables modeled by the baseline, only. ' # 'Use SKIP to explicitly ignore analysis of overriden ' # 'variables') return varnos, seen_optimized
def retrieve_variable(var): if not core.bayesdb_has_variable(bdb, population_id, generator_id, var): raise BQLError(bdb, 'No such population variable: %s' % (var, )) return core.bayesdb_variable_number(bdb, population_id, generator_id, var)
def _retrieve_analyze_variables(bdb, generator_id, ast): population_id = core.bayesdb_generator_population(bdb, generator_id) # Transitions all variables by default. variables = None # Exactly 1 VARIABLES or SKIP clause supported for simplicity. seen_variables, seen_skip, seen_optimized = False, False, False for clause in ast: # Transition user specified variables only. if isinstance(clause, cgpm_analyze.parse.Variables): if seen_variables or seen_skip: raise BQLError( bdb, 'Only 1 VARIABLES or SKIP clause allowed in ANALYZE') seen_variables = True included = set() unknown = set() for var in clause.vars: if not core.bayesdb_has_variable(bdb, population_id, generator_id, var): unknown.add(var) included.add(var) if unknown: raise BQLError( bdb, 'Unknown variables in ANALYZE: %r' % (sorted(unknown), )) variables = sorted(included) # Transition all variables except user specified skip. elif isinstance(clause, cgpm_analyze.parse.Skip): if seen_variables or seen_skip: raise BQLError( bdb, 'Only 1 VARIABLES or SKIP clause allowed in ANALYZE') seen_skip = True excluded = set() unknown = set() for var in clause.vars: if not core.bayesdb_has_variable(bdb, population_id, generator_id, var): unknown.add(var) excluded.add(var) if unknown: raise BQLError( bdb, 'Unknown variables in ANALYZE: %r' % (sorted(unknown), )) all_vars = core.bayesdb_variable_names(bdb, population_id, generator_id) variables = sorted(set(all_vars) - excluded) # OPTIMIZED is incompatible with any other clause. elif isinstance(clause, cgpm_analyze.parse.Optimized): seen_optimized = True # Unknown/impossible clause. else: raise BQLError(bdb, 'Unknown clause in ANALYZE: %s.' % (ast, )) # OPTIMIZED is incompatible with any other clause. if seen_optimized: if seen_variables or seen_skip: raise BQLError(bdb, 'OPTIMIZED incompatible with other clauses.') variable_numbers = [ core.bayesdb_variable_number(bdb, population_id, generator_id, v) for v in variables ] if variables else None return (variable_numbers, seen_optimized)
population_id = core.bayesdb_get_population(bdb, population) for cmd in phrase.commands: if isinstance(cmd, ast.AlterPopStatType): # Check the no metamodels are defined for this population. generators = core.bayesdb_population_generators( bdb, population_id) if generators: raise BQLError( bdb, 'Cannot update statistical types ' 'for population %s, it has metamodels: %s' % ( repr(population), repr(generators), )) # Check all the variables are in the population. unknown = [ c for c in cmd.names if not core.bayesdb_has_variable( bdb, population_id, None, c) ] if unknown: raise BQLError( bdb, 'No such variables in population' ': %s' % (repr(unknown))) # Check the statistical type is valid. if not core.bayesdb_has_stattype(bdb, cmd.stattype): raise BQLError( bdb, 'Invalid statistical type' ': %r' % (repr(cmd.stattype), )) # Perform the stattype update. colnos = [ core.bayesdb_variable_number(bdb, population_id, None, c) for c in cmd.names ]
def execute_phrase(bdb, phrase, bindings=()): """Execute the BQL AST phrase `phrase` and return a cursor of results.""" if isinstance(phrase, ast.Parametrized): n_numpar = phrase.n_numpar nampar_map = phrase.nampar_map phrase = phrase.phrase assert 0 < n_numpar else: n_numpar = 0 nampar_map = None # Ignore extraneous bindings. XXX Bad idea? if ast.is_query(phrase): # Compile the query in the transaction in case we need to # execute subqueries to determine column lists. Compiling is # a quick tree descent, so this should be fast. out = compiler.Output(n_numpar, nampar_map, bindings) with bdb.savepoint(): compiler.compile_query(bdb, phrase, out) winders, unwinders = out.getwindings() return execute_wound(bdb, winders, unwinders, out.getvalue(), out.getbindings()) if isinstance(phrase, ast.Begin): txn.bayesdb_begin_transaction(bdb) return empty_cursor(bdb) if isinstance(phrase, ast.Rollback): txn.bayesdb_rollback_transaction(bdb) return empty_cursor(bdb) if isinstance(phrase, ast.Commit): txn.bayesdb_commit_transaction(bdb) return empty_cursor(bdb) if isinstance(phrase, ast.CreateTabAs): assert ast.is_query(phrase.query) with bdb.savepoint(): if core.bayesdb_has_table(bdb, phrase.name): if phrase.ifnotexists: return empty_cursor(bdb) else: raise BQLError( bdb, 'Name already defined as table: %s' % (repr(phrase.name), )) out = compiler.Output(n_numpar, nampar_map, bindings) qt = sqlite3_quote_name(phrase.name) temp = 'TEMP ' if phrase.temp else '' ifnotexists = 'IF NOT EXISTS ' if phrase.ifnotexists else '' out.write('CREATE %sTABLE %s%s AS ' % (temp, ifnotexists, qt)) compiler.compile_query(bdb, phrase.query, out) winders, unwinders = out.getwindings() with compiler.bayesdb_wind(bdb, winders, unwinders): bdb.sql_execute(out.getvalue(), out.getbindings()) return empty_cursor(bdb) if isinstance(phrase, ast.CreateTabCsv): with bdb.savepoint(): table_exists = core.bayesdb_has_table(bdb, phrase.name) if table_exists: if phrase.ifnotexists: return empty_cursor(bdb) else: raise BQLError( bdb, 'Table already exists: %s' % (repr(phrase.name), )) bayesdb_read_csv_file(bdb, phrase.name, phrase.csv, header=True, create=True) return empty_cursor(bdb) if isinstance(phrase, ast.CreateTabSimModels): assert isinstance(phrase.simulation, ast.SimulateModels) with bdb.savepoint(): # Check if table exists. if core.bayesdb_has_table(bdb, phrase.name): if phrase.ifnotexists: return empty_cursor(bdb) raise BQLError( bdb, 'Name already defined as table: %s' % (phrase.name), ) # Set up schema and create the new table. qn = sqlite3_quote_name(phrase.name) qcns = map(sqlite3_quote_name, [ simcol.name if simcol.name is not None else str(simcol.col) for simcol in phrase.simulation.columns ]) temp = '' if phrase.temp is None else 'TEMP' bdb.sql_execute(''' CREATE %s TABLE %s (%s) ''' % (temp, qn, str.join(',', qcns))) # Retrieve the rows. rows = simulate_models_rows(bdb, phrase.simulation) # Insert the rows into the table. insert_sql = ''' INSERT INTO %s (%s) VALUES (%s) ''' % (qn, ','.join(qcns), ','.join('?' for qcn in qcns)) for row in rows: bdb.sql_execute(insert_sql, row) return empty_cursor(bdb) if isinstance(phrase, ast.DropTab): with bdb.savepoint(): sql = 'SELECT COUNT(*) FROM bayesdb_population WHERE tabname = ?' cursor = bdb.sql_execute(sql, (phrase.name, )) if 0 < cursor_value(cursor): raise BQLError( bdb, 'Table still in use by populations: %s' % (repr(phrase.name), )) bdb.sql_execute('DELETE FROM bayesdb_column WHERE tabname = ?', (phrase.name, )) ifexists = 'IF EXISTS ' if phrase.ifexists else '' qt = sqlite3_quote_name(phrase.name) return bdb.sql_execute('DROP TABLE %s%s' % (ifexists, qt)) if isinstance(phrase, ast.AlterTab): with bdb.savepoint(): table = phrase.table if not core.bayesdb_has_table(bdb, table): raise BQLError(bdb, 'No such table: %s' % (repr(table), )) for cmd in phrase.commands: if isinstance(cmd, ast.AlterTabRenameTab): # If the names differ only in case, we have to do # some extra work because SQLite will reject the # table rename. Note that we may even have table # == cmd.name here, but if the stored table name # differs in case from cmd.name, we want to update # it anyway. if casefold(table) == casefold(cmd.name): # Go via a temporary table. temp = table + '_temp' while core.bayesdb_has_table(bdb, temp): temp += '_temp' rename_table(bdb, table, temp) rename_table(bdb, temp, cmd.name) else: # Make sure nothing else has this name and # rename it. if core.bayesdb_has_table(bdb, cmd.name): raise BQLError( bdb, 'Name already defined as table' ': %s' % (repr(cmd.name), )) rename_table(bdb, table, cmd.name) # Remember the new name for subsequent commands. table = cmd.name elif isinstance(cmd, ast.AlterTabRenameCol): # XXX Need to deal with this in the compiler. raise NotImplementedError('Renaming columns' ' not yet implemented.') # Make sure the old name exist and the new name does not. old_folded = casefold(cmd.old) new_folded = casefold(cmd.new) if old_folded != new_folded: if not core.bayesdb_table_has_column( bdb, table, cmd.old): raise BQLError( bdb, 'No such column in table %s' ': %s' % (repr(table), repr(cmd.old))) if core.bayesdb_table_has_column(bdb, table, cmd.new): raise BQLError( bdb, 'Column already exists' ' in table %s: %s' % (repr(table), repr(cmd.new))) # Update bayesdb_column. Everything else refers # to columns by (tabname, colno) pairs rather than # by names. update_column_sql = ''' UPDATE bayesdb_column SET name = :new WHERE tabname = :table AND name = :old ''' total_changes = bdb._sqlite3.totalchanges() bdb.sql_execute(update_column_sql, { 'table': table, 'old': cmd.old, 'new': cmd.new, }) assert bdb._sqlite3.totalchanges() - total_changes == 1 # ...except metamodels may have the (case-folded) # name cached. if old_folded != new_folded: generators_sql = ''' SELECT id FROM bayesdb_generator WHERE tabname = ? ''' cursor = bdb.sql_execute(generators_sql, (table, )) for (generator_id, ) in cursor: metamodel = core.bayesdb_generator_metamodel( bdb, generator_id) metamodel.rename_column(bdb, generator_id, old_folded, new_folded) else: assert False, 'Invalid alter table command: %s' % \ (cmd,) return empty_cursor(bdb) if isinstance(phrase, ast.GuessSchema): if not core.bayesdb_has_table(bdb, phrase.table): raise BQLError(bdb, 'No such table : %s' % phrase.table) schema = guess.guess_to_schema(guess.bayesdb_guess_stattypes, bdb, phrase.table) # Print schema to console, so user can edit it and/or copy/paste it into # the schema definition when creating a population. print schema return empty_cursor(bdb) if isinstance(phrase, ast.CreatePop): with bdb.savepoint(): _create_population(bdb, phrase) return empty_cursor(bdb) if isinstance(phrase, ast.DropPop): with bdb.savepoint(): if not core.bayesdb_has_population(bdb, phrase.name): if phrase.ifexists: return empty_cursor(bdb) raise BQLError(bdb, 'No such population: %r' % (phrase.name, )) population_id = core.bayesdb_get_population(bdb, phrase.name) if core.bayesdb_population_generators(bdb, population_id): raise BQLError( bdb, 'Population still has generators: %r' % (phrase.name, )) # XXX helpful error checking if generators still exist # XXX check change counts bdb.sql_execute( ''' DELETE FROM bayesdb_variable WHERE population_id = ? ''', (population_id, )) bdb.sql_execute( ''' DELETE FROM bayesdb_population WHERE id = ? ''', (population_id, )) return empty_cursor(bdb) if isinstance(phrase, ast.AlterPop): with bdb.savepoint(): population = phrase.population if not core.bayesdb_has_population(bdb, population): raise BQLError(bdb, 'No such population: %s' % (repr(population), )) population_id = core.bayesdb_get_population(bdb, population) for cmd in phrase.commands: if isinstance(cmd, ast.AlterPopStatType): # Check the no metamodels are defined for this population. generators = core.bayesdb_population_generators( bdb, population_id) if generators: raise BQLError( bdb, 'Cannot update statistical types ' 'for population %s, it has metamodels: %s' % ( repr(population), repr(generators), )) # Check all the variables are in the population. unknown = [ c for c in cmd.names if not core.bayesdb_has_variable( bdb, population_id, None, c) ] if unknown: raise BQLError( bdb, 'No such variables in population' ': %s' % (repr(unknown))) # Check the statistical type is valid. if not core.bayesdb_has_stattype(bdb, cmd.stattype): raise BQLError( bdb, 'Invalid statistical type' ': %r' % (repr(cmd.stattype), )) # Perform the stattype update. colnos = [ core.bayesdb_variable_number(bdb, population_id, None, c) for c in cmd.names ] qcolnos = ','.join('%d' % (colno, ) for colno in colnos) update_stattype_sql = ''' UPDATE bayesdb_variable SET stattype = ? WHERE population_id = ? AND colno IN (%s) ''' % (qcolnos, ) bdb.sql_execute(update_stattype_sql, ( casefold(cmd.stattype), population_id, )) else: assert False, 'Invalid ALTER POPULATION command: %s' % \ (repr(cmd),) return empty_cursor(bdb) if isinstance(phrase, ast.CreateGen): # Find the population. if not core.bayesdb_has_population(bdb, phrase.population): raise BQLError(bdb, 'No such population: %r' % (phrase.population, )) population_id = core.bayesdb_get_population(bdb, phrase.population) table = core.bayesdb_population_table(bdb, population_id) # Find the metamodel, or use the default. metamodel_name = phrase.metamodel if phrase.metamodel is None: metamodel_name = 'cgpm' if metamodel_name not in bdb.metamodels: raise BQLError(bdb, 'No such metamodel: %s' % (repr(metamodel_name), )) metamodel = bdb.metamodels[metamodel_name] with bdb.savepoint(): if core.bayesdb_has_generator(bdb, population_id, phrase.name): if not phrase.ifnotexists: raise BQLError( bdb, 'Name already defined as generator: %s' % (repr(phrase.name), )) else: # Insert a record into bayesdb_generator and get the # assigned id. bdb.sql_execute( ''' INSERT INTO bayesdb_generator (name, tabname, population_id, metamodel) VALUES (?, ?, ?, ?) ''', (phrase.name, table, population_id, metamodel.name())) generator_id = core.bayesdb_get_generator( bdb, population_id, phrase.name) # Populate bayesdb_generator_column. # # XXX Omit needless bayesdb_generator_column table -- # Github issue #441. bdb.sql_execute( ''' INSERT INTO bayesdb_generator_column (generator_id, colno, stattype) SELECT :generator_id, colno, stattype FROM bayesdb_variable WHERE population_id = :population_id AND generator_id IS NULL ''', { 'generator_id': generator_id, 'population_id': population_id, }) # Do any metamodel-specific initialization. metamodel.create_generator(bdb, generator_id, phrase.schema, baseline=phrase.baseline) # Populate bayesdb_generator_column with any latent # variables that metamodel.create_generator has added # with bayesdb_add_latent. bdb.sql_execute( ''' INSERT INTO bayesdb_generator_column (generator_id, colno, stattype) SELECT :generator_id, colno, stattype FROM bayesdb_variable WHERE population_id = :population_id AND generator_id = :generator_id ''', { 'generator_id': generator_id, 'population_id': population_id, }) # All done. Nothing to return. return empty_cursor(bdb) if isinstance(phrase, ast.DropGen): with bdb.savepoint(): if not core.bayesdb_has_generator(bdb, None, phrase.name): if phrase.ifexists: return empty_cursor(bdb) raise BQLError(bdb, 'No such generator: %s' % (repr(phrase.name), )) generator_id = core.bayesdb_get_generator(bdb, None, phrase.name) metamodel = core.bayesdb_generator_metamodel(bdb, generator_id) # Metamodel-specific destruction. metamodel.drop_generator(bdb, generator_id) # Drop the columns, models, and, finally, generator. drop_columns_sql = ''' DELETE FROM bayesdb_generator_column WHERE generator_id = ? ''' bdb.sql_execute(drop_columns_sql, (generator_id, )) drop_model_sql = ''' DELETE FROM bayesdb_generator_model WHERE generator_id = ? ''' bdb.sql_execute(drop_model_sql, (generator_id, )) drop_generator_sql = ''' DELETE FROM bayesdb_generator WHERE id = ? ''' bdb.sql_execute(drop_generator_sql, (generator_id, )) return empty_cursor(bdb) if isinstance(phrase, ast.AlterGen): with bdb.savepoint(): generator = phrase.generator if not core.bayesdb_has_generator(bdb, None, generator): raise BQLError(bdb, 'No such generator: %s' % (repr(generator), )) generator_id = core.bayesdb_get_generator(bdb, None, generator) for cmd in phrase.commands: if isinstance(cmd, ast.AlterGenRenameGen): # Make sure nothing else has this name. if casefold(generator) != casefold(cmd.name): if core.bayesdb_has_table(bdb, cmd.name): raise BQLError( bdb, 'Name already defined as table' ': %s' % (repr(cmd.name), )) if core.bayesdb_has_generator(bdb, None, cmd.name): raise BQLError( bdb, 'Name already defined' ' as generator: %s' % (repr(cmd.name), )) # Update bayesdb_generator. Everything else # refers to it by id. update_generator_sql = ''' UPDATE bayesdb_generator SET name = ? WHERE id = ? ''' total_changes = bdb._sqlite3.totalchanges() bdb.sql_execute(update_generator_sql, (cmd.name, generator_id)) assert bdb._sqlite3.totalchanges() - total_changes == 1 # Remember the new name for subsequent commands. generator = cmd.name else: assert False, 'Invalid ALTER GENERATOR command: %s' % \ (repr(cmd),) return empty_cursor(bdb) if isinstance(phrase, ast.InitModels): if not core.bayesdb_has_generator(bdb, None, phrase.generator): raise BQLError(bdb, 'No such generator: %s' % (phrase.generator, )) generator_id = core.bayesdb_get_generator(bdb, None, phrase.generator) modelnos = range(phrase.nmodels) with bdb.savepoint(): # Find the model numbers. Omit existing ones for # ifnotexists; reject existing ones otherwise. if phrase.ifnotexists: modelnos = set(modelno for modelno in modelnos if not core.bayesdb_generator_has_model( bdb, generator_id, modelno)) else: existing = set(modelno for modelno in modelnos if core.bayesdb_generator_has_model( bdb, generator_id, modelno)) if 0 < len(existing): raise BQLError( bdb, 'Generator %s already has models: %s' % (repr(phrase.generator), sorted(existing))) # Stop now if there's nothing to initialize. if len(modelnos) == 0: return # Create the bayesdb_generator_model records. modelnos = sorted(modelnos) insert_model_sql = ''' INSERT INTO bayesdb_generator_model (generator_id, modelno, iterations) VALUES (:generator_id, :modelno, :iterations) ''' for modelno in modelnos: bdb.sql_execute( insert_model_sql, { 'generator_id': generator_id, 'modelno': modelno, 'iterations': 0, }) # Do metamodel-specific initialization. metamodel = core.bayesdb_generator_metamodel(bdb, generator_id) metamodel.initialize_models(bdb, generator_id, modelnos) return empty_cursor(bdb) if isinstance(phrase, ast.AnalyzeModels): if not phrase.wait: raise NotImplementedError('No background analysis -- use WAIT.') # WARNING: It is the metamodel's responsibility to work in a # transaction. # # WARNING: It is the metamodel's responsibility to update the # iteration count in bayesdb_generator_model records. # # We do this so that the metamodel can save incremental # progress in case of ^C in the middle. # # XXX Put these warning somewhere more appropriate. if not core.bayesdb_has_generator(bdb, None, phrase.generator): raise BQLError(bdb, 'No such generator: %s' % (phrase.generator, )) generator_id = core.bayesdb_get_generator(bdb, None, phrase.generator) metamodel = core.bayesdb_generator_metamodel(bdb, generator_id) # XXX Should allow parameters for iterations and ckpt/iter. metamodel.analyze_models(bdb, generator_id, modelnos=phrase.modelnos, iterations=phrase.iterations, max_seconds=phrase.seconds, ckpt_iterations=phrase.ckpt_iterations, ckpt_seconds=phrase.ckpt_seconds, program=phrase.program) return empty_cursor(bdb) if isinstance(phrase, ast.DropModels): with bdb.savepoint(): generator_id = core.bayesdb_get_generator(bdb, None, phrase.generator) metamodel = core.bayesdb_generator_metamodel(bdb, generator_id) modelnos = None if phrase.modelnos is not None: lookup_model_sql = ''' SELECT COUNT(*) FROM bayesdb_generator_model WHERE generator_id = :generator_id AND modelno = :modelno ''' modelnos = sorted(list(phrase.modelnos)) for modelno in modelnos: cursor = bdb.sql_execute(lookup_model_sql, { 'generator_id': generator_id, 'modelno': modelno, }) if cursor_value(cursor) == 0: raise BQLError( bdb, 'No such model' ' in generator %s: %s' % (repr(phrase.generator), repr(modelno))) metamodel.drop_models(bdb, generator_id, modelnos=modelnos) if modelnos is None: drop_models_sql = ''' DELETE FROM bayesdb_generator_model WHERE generator_id = ? ''' bdb.sql_execute(drop_models_sql, (generator_id, )) else: drop_model_sql = ''' DELETE FROM bayesdb_generator_model WHERE generator_id = :generator_id AND modelno = :modelno ''' for modelno in modelnos: bdb.sql_execute(drop_model_sql, { 'generator_id': generator_id, 'modelno': modelno, }) return empty_cursor(bdb) assert False # XXX
def execute_phrase(bdb, phrase, bindings=()): """Execute the BQL AST phrase `phrase` and return a cursor of results.""" if isinstance(phrase, ast.Parametrized): n_numpar = phrase.n_numpar nampar_map = phrase.nampar_map phrase = phrase.phrase assert 0 < n_numpar else: n_numpar = 0 nampar_map = None # Ignore extraneous bindings. XXX Bad idea? if ast.is_query(phrase): # Compile the query in the transaction in case we need to # execute subqueries to determine column lists. Compiling is # a quick tree descent, so this should be fast. out = compiler.Output(n_numpar, nampar_map, bindings) with bdb.savepoint(): compiler.compile_query(bdb, phrase, out) winders, unwinders = out.getwindings() return execute_wound(bdb, winders, unwinders, out.getvalue(), out.getbindings()) if isinstance(phrase, ast.Begin): txn.bayesdb_begin_transaction(bdb) return empty_cursor(bdb) if isinstance(phrase, ast.Rollback): txn.bayesdb_rollback_transaction(bdb) return empty_cursor(bdb) if isinstance(phrase, ast.Commit): txn.bayesdb_commit_transaction(bdb) return empty_cursor(bdb) if isinstance(phrase, ast.CreateTabAs): assert ast.is_query(phrase.query) with bdb.savepoint(): if core.bayesdb_has_table(bdb, phrase.name): if phrase.ifnotexists: return empty_cursor(bdb) else: raise BQLError(bdb, 'Name already defined as table: %s' % (repr(phrase.name),)) out = compiler.Output(n_numpar, nampar_map, bindings) qt = sqlite3_quote_name(phrase.name) temp = 'TEMP ' if phrase.temp else '' ifnotexists = 'IF NOT EXISTS ' if phrase.ifnotexists else '' out.write('CREATE %sTABLE %s%s AS ' % (temp, ifnotexists, qt)) compiler.compile_query(bdb, phrase.query, out) winders, unwinders = out.getwindings() with compiler.bayesdb_wind(bdb, winders, unwinders): bdb.sql_execute(out.getvalue(), out.getbindings()) return empty_cursor(bdb) if isinstance(phrase, ast.CreateTabCsv): with bdb.savepoint(): table_exists = core.bayesdb_has_table(bdb, phrase.name) if table_exists: if phrase.ifnotexists: return empty_cursor(bdb) else: raise BQLError(bdb, 'Table already exists: %s' % (repr(phrase.name),)) bayesdb_read_csv_file( bdb, phrase.name, phrase.csv, header=True, create=True) return empty_cursor(bdb) if isinstance(phrase, ast.DropTab): with bdb.savepoint(): sql = 'SELECT COUNT(*) FROM bayesdb_population WHERE tabname = ?' cursor = bdb.sql_execute(sql, (phrase.name,)) if 0 < cursor_value(cursor): raise BQLError(bdb, 'Table still in use by populations: %s' % (repr(phrase.name),)) bdb.sql_execute('DELETE FROM bayesdb_column WHERE tabname = ?', (phrase.name,)) ifexists = 'IF EXISTS ' if phrase.ifexists else '' qt = sqlite3_quote_name(phrase.name) return bdb.sql_execute('DROP TABLE %s%s' % (ifexists, qt)) if isinstance(phrase, ast.AlterTab): with bdb.savepoint(): table = phrase.table if not core.bayesdb_has_table(bdb, table): raise BQLError(bdb, 'No such table: %s' % (repr(table),)) for cmd in phrase.commands: if isinstance(cmd, ast.AlterTabRenameTab): # If the names differ only in case, we have to do # some extra work because SQLite will reject the # table rename. Note that we may even have table # == cmd.name here, but if the stored table name # differs in case from cmd.name, we want to update # it anyway. if casefold(table) == casefold(cmd.name): # Go via a temporary table. temp = table + '_temp' while core.bayesdb_has_table(bdb, temp): temp += '_temp' rename_table(bdb, table, temp) rename_table(bdb, temp, cmd.name) else: # Make sure nothing else has this name and # rename it. if core.bayesdb_has_table(bdb, cmd.name): raise BQLError(bdb, 'Name already defined as table: %s' % (repr(cmd.name),)) rename_table(bdb, table, cmd.name) # If table has implicit population, rename it too. if core.bayesdb_table_has_implicit_population( bdb, cmd.name): populations = \ core.bayesdb_table_populations(bdb, cmd.name) assert len(populations) == 1 population_name = core.bayesdb_population_name( bdb, populations[0]) qt = sqlite3_quote_name(cmd.name) qp = sqlite3_quote_name(population_name) bdb.execute('ALTER POPULATION %s RENAME TO %s' % (qp, qt)) # Remember the new name for subsequent commands. table = cmd.name elif isinstance(cmd, ast.AlterTabRenameCol): # XXX Need to deal with this in the compiler. raise NotImplementedError('Renaming columns' ' not yet implemented.') # Make sure the old name exist and the new name does not. old_folded = casefold(cmd.old) new_folded = casefold(cmd.new) if old_folded != new_folded: if not core.bayesdb_table_has_column(bdb, table, cmd.old): raise BQLError(bdb, 'No such column in table %s' ': %s' % (repr(table), repr(cmd.old))) if core.bayesdb_table_has_column(bdb, table, cmd.new): raise BQLError(bdb, 'Column already exists' ' in table %s: %s' % (repr(table), repr(cmd.new))) # Update bayesdb_column. Everything else refers # to columns by (tabname, colno) pairs rather than # by names. update_column_sql = ''' UPDATE bayesdb_column SET name = :new WHERE tabname = :table AND name = :old ''' total_changes = bdb._sqlite3.totalchanges() bdb.sql_execute(update_column_sql, { 'table': table, 'old': cmd.old, 'new': cmd.new, }) assert bdb._sqlite3.totalchanges() - total_changes == 1 # ...except backends may have the (case-folded) name cached. if old_folded != new_folded: populations_sql = ''' SELECT id FROM bayesdb_population WHERE tabname = ? ''' cursor = bdb.sql_execute(populations_sql, (table,)) generators = [ core.bayesdb_population_generators( bdb, population_id) for (population_id,) in cursor ] for generator_id in set(generators): backend = core.bayesdb_generator_backend(bdb, generator_id) backend.rename_column(bdb, generator_id, old_folded, new_folded) else: assert False, 'Invalid alter table command: %s' % \ (cmd,) return empty_cursor(bdb) if isinstance(phrase, ast.GuessSchema): if not core.bayesdb_has_table(bdb, phrase.table): raise BQLError(bdb, 'No such table : %s' % phrase.table) out = compiler.Output(0, {}, {}) with bdb.savepoint(): qt = sqlite3_quote_name(phrase.table) temptable = bdb.temp_table_name() qtt = sqlite3_quote_name(temptable) cursor = bdb.sql_execute('SELECT * FROM %s' % (qt,)) column_names = [d[0] for d in cursor.description] rows = cursor.fetchall() stattypes = bayesdb_guess_stattypes(column_names, rows) distinct_value_counts = [ len(set([row[i] for row in rows])) for i in range(len(column_names)) ] out.winder(''' CREATE TEMP TABLE %s ( column TEXT, stattype TEXT, num_distinct INTEGER, reason TEXT ) ''' % (qtt,), ()) for cn, st, ct in zip(column_names, stattypes, distinct_value_counts): out.winder(''' INSERT INTO %s VALUES (?, ?, ?, ?) ''' % (qtt), (cn, st[0], ct, st[1])) out.write('SELECT * FROM %s' % (qtt,)) out.unwinder('DROP TABLE %s' % (qtt,), ()) winders, unwinders = out.getwindings() return execute_wound( bdb, winders, unwinders, out.getvalue(), out.getbindings()) if isinstance(phrase, ast.CreatePop): with bdb.savepoint(): _create_population(bdb, phrase) return empty_cursor(bdb) if isinstance(phrase, ast.DropPop): with bdb.savepoint(): if not core.bayesdb_has_population(bdb, phrase.name): if phrase.ifexists: return empty_cursor(bdb) raise BQLError(bdb, 'No such population: %r' % (phrase.name,)) population_id = core.bayesdb_get_population(bdb, phrase.name) generator_ids = core.bayesdb_population_generators( bdb, population_id) if generator_ids: generators = [core.bayesdb_generator_name(bdb, gid) for gid in generator_ids] raise BQLError(bdb, 'Population %r still has generators: %r' % (phrase.name, generators)) # XXX helpful error checking if generators still exist # XXX check change counts bdb.sql_execute(''' DELETE FROM bayesdb_variable WHERE population_id = ? ''', (population_id,)) bdb.sql_execute(''' DELETE FROM bayesdb_population WHERE id = ? ''', (population_id,)) return empty_cursor(bdb) if isinstance(phrase, ast.AlterPop): with bdb.savepoint(): population = phrase.population if not core.bayesdb_has_population(bdb, population): raise BQLError(bdb, 'No such population: %s' % (repr(population),)) population_id = core.bayesdb_get_population(bdb, population) for cmd in phrase.commands: if isinstance(cmd, ast.AlterPopRenamePop): table = core.bayesdb_population_table(bdb, population_id) # Prevent renaming of implicit population directly, unless # being called by ast.AlterTabRenameTab in which case the # table name and population name will not be matching. if core.bayesdb_population_is_implicit(bdb, population_id) \ and casefold(population) == casefold(table): raise BQLError(bdb, 'Cannot rename implicit' 'population %s; rename base table instead' % (population,)) # Make sure nothing else has this name. if casefold(population) != casefold(cmd.name): if core.bayesdb_has_population(bdb, cmd.name): raise BQLError(bdb, 'Name already defined as population' ': %s' % (repr(cmd.name),)) # Update bayesdb_population. Everything else # refers to it by id. update_generator_sql = ''' UPDATE bayesdb_population SET name = ? WHERE id = ? ''' total_changes = bdb._sqlite3.totalchanges() bdb.sql_execute(update_generator_sql, (cmd.name, population_id)) assert bdb._sqlite3.totalchanges() - total_changes == 1 # If population has implicit generator, rename it too. if core.bayesdb_population_has_implicit_generator( bdb, population_id): generators = core.bayesdb_population_generators( bdb, population_id) assert len(generators) == 1 generator_name = core.bayesdb_generator_name( bdb, generators[0]) qp = sqlite3_quote_name(cmd.name) qg = sqlite3_quote_name(generator_name) bdb.execute('ALTER GENERATOR %s RENAME TO %s' % (qg, qp,)) # Remember the new name for subsequent commands. population = cmd.name elif isinstance(cmd, ast.AlterPopAddVar): # Ensure column exists in base table. table = core.bayesdb_population_table(bdb, population_id) if not core.bayesdb_table_has_column( bdb, table, cmd.name): raise BQLError(bdb, 'No such variable in base table: %s' % (cmd.name)) # Ensure variable not already in population. if core.bayesdb_has_variable( bdb, population_id, None, cmd.name): raise BQLError(bdb, 'Variable already in population: %s' % (cmd.name)) # Ensure there is at least observation in the column. qt = sqlite3_quote_name(table) qc = sqlite3_quote_name(cmd.name) cursor = bdb.sql_execute( 'SELECT COUNT(*) FROM %s WHERE %s IS NOT NULL' % (qt, qc)) if cursor_value(cursor) == 0: raise BQLError(bdb, 'Cannot add variable without any values: %s' % (cmd.name)) # If stattype is None, guess. if cmd.stattype is None: cursor = bdb.sql_execute( 'SELECT %s FROM %s' % (qc, qt)) rows = cursor.fetchall() [stattype, reason] = bayesdb_guess_stattypes( [cmd.name], rows)[0] # Fail if trying to model a key. if stattype == 'key': raise BQLError(bdb, 'Values in column %s appear to be keys.' % (cmd.name,)) # Fail if cannot determine a stattype. elif stattype == 'ignore': raise BQLError(bdb, 'Failed to determine a stattype for %s, ' 'please specify one manually.' % (cmd.name,)) # If user specified stattype, ensure it exists. elif not core.bayesdb_has_stattype(bdb, cmd.stattype): raise BQLError(bdb, 'Invalid stattype: %s' % (cmd.stattype)) else: stattype = cmd.stattype # Check that strings are not being modeled as numerical. if stattype == 'numerical' \ and _column_contains_string(bdb, table, cmd.name): raise BQLError(bdb, 'Numerical column contains string values: %r ' % (qc,)) with bdb.savepoint(): # Add the variable to the population. core.bayesdb_add_variable( bdb, population_id, cmd.name, stattype) colno = core.bayesdb_variable_number( bdb, population_id, None, cmd.name) # Add the variable to each (initialized) generator in # the population. generator_ids = filter( lambda g: core.bayesdb_generator_modelnos(bdb, g), core.bayesdb_population_generators( bdb, population_id), ) for generator_id in generator_ids: backend = core.bayesdb_generator_backend( bdb, generator_id) backend.add_column(bdb, generator_id, colno) elif isinstance(cmd, ast.AlterPopStatType): # Check the no generators are defined for this population. generators = core.bayesdb_population_generators( bdb, population_id) if generators: raise BQLError(bdb, 'Cannot update statistical types for population ' '%s, it has generators: %s' % (repr(population), repr(generators),)) # Check all the variables are in the population. unknown = [ c for c in cmd.names if not core.bayesdb_has_variable(bdb, population_id, None, c) ] if unknown: raise BQLError(bdb, 'No such variables in population: %s' % (repr(unknown))) # Check the statistical type is valid. if not core.bayesdb_has_stattype(bdb, cmd.stattype): raise BQLError(bdb, 'Invalid statistical type: %r' % (repr(cmd.stattype),)) # Check that strings are not being modeled as numerical. if cmd.stattype == 'numerical': table = core.bayesdb_population_table( bdb, population_id) numerical_string_vars = [ col for col in cmd.names if _column_contains_string(bdb, table, col) ] if numerical_string_vars: raise BQLError(bdb, 'Columns with string values modeled as ' 'numerical: %r' % (numerical_string_vars,)) # Perform the stattype update. colnos = [ core.bayesdb_variable_number( bdb, population_id, None, c) for c in cmd.names ] qcolnos = ','.join('%d' % (colno,) for colno in colnos) update_stattype_sql = ''' UPDATE bayesdb_variable SET stattype = ? WHERE population_id = ? AND colno IN (%s) ''' % (qcolnos,) bdb.sql_execute( update_stattype_sql, (casefold(cmd.stattype), population_id,)) else: assert False, 'Invalid ALTER POPULATION command: %s' % \ (repr(cmd),) return empty_cursor(bdb) if isinstance(phrase, ast.CreateGen): # Find the population. if not core.bayesdb_has_population(bdb, phrase.population): raise BQLError(bdb, 'No such population: %r' % (phrase.population,)) population_id = core.bayesdb_get_population(bdb, phrase.population) # Find the backend, or use the default. backend_name = phrase.backend if phrase.backend is None: backend_name = 'cgpm' if backend_name not in bdb.backends: raise BQLError(bdb, 'No such backend: %s' % (repr(backend_name),)) backend = bdb.backends[backend_name] # Retrieve the (possibility implicit) generator name. generator_name = phrase.name or phrase.population implicit = 1 if phrase.name is None else 0 with bdb.savepoint(): if core.bayesdb_has_generator(bdb, population_id, generator_name): if not phrase.ifnotexists: raise BQLError( bdb, 'Name already defined as generator: %s' % (repr(generator_name),)) else: # Insert a record into bayesdb_generator and get the # assigned id. bdb.sql_execute(''' INSERT INTO bayesdb_generator (name, population_id, backend, implicit) VALUES (?, ?, ?, ?) ''', (generator_name, population_id, backend.name(), implicit)) generator_id = core.bayesdb_get_generator( bdb, population_id, generator_name) # Do any backend-specific initialization. backend.create_generator(bdb, generator_id, phrase.schema) # All done. Nothing to return. return empty_cursor(bdb) if isinstance(phrase, ast.DropGen): with bdb.savepoint(): if not core.bayesdb_has_generator(bdb, None, phrase.name): if phrase.ifexists: return empty_cursor(bdb) raise BQLError(bdb, 'No such generator: %s' % (repr(phrase.name),)) generator_id = core.bayesdb_get_generator(bdb, None, phrase.name) backend = core.bayesdb_generator_backend(bdb, generator_id) # Backend-specific destruction. backend.drop_generator(bdb, generator_id) # Drop latent variables, models, and, finally, generator. drop_columns_sql = ''' DELETE FROM bayesdb_variable WHERE generator_id = ? ''' bdb.sql_execute(drop_columns_sql, (generator_id,)) drop_model_sql = ''' DELETE FROM bayesdb_generator_model WHERE generator_id = ? ''' bdb.sql_execute(drop_model_sql, (generator_id,)) drop_generator_sql = ''' DELETE FROM bayesdb_generator WHERE id = ? ''' bdb.sql_execute(drop_generator_sql, (generator_id,)) return empty_cursor(bdb) if isinstance(phrase, ast.AlterGen): with bdb.savepoint(): generator = phrase.generator if not core.bayesdb_has_generator(bdb, None, generator): raise BQLError(bdb, 'No such generator: %s' % (repr(generator),)) generator_id = core.bayesdb_get_generator(bdb, None, generator) cmds_generic = [] for cmd in phrase.commands: if isinstance(cmd, ast.AlterGenRenameGen): population_id = core.bayesdb_generator_population( bdb, generator_id) population = core.bayesdb_population_name( bdb, population_id) # Prevent renaming of implicit generator directly, unless # being called by ast.AlterPopRenamePop in which case the # population name and generator name will not be matching. if core.bayesdb_population_is_implicit(bdb, generator_id) \ and casefold(generator) == casefold(population): raise BQLError(bdb, 'Cannot rename implicit ' 'generator; rename base population instead') # Disable modelnos with AlterGenRenameGen. if phrase.modelnos is not None: raise BQLError(bdb, 'Cannot specify models for RENAME') # Make sure nothing else has this name. if casefold(generator) != casefold(cmd.name): if core.bayesdb_has_generator(bdb, None, cmd.name): raise BQLError(bdb, 'Name already defined' ' as generator: %s' % (repr(cmd.name),)) # Update bayesdb_generator. Everything else # refers to it by id. update_generator_sql = ''' UPDATE bayesdb_generator SET name = ? WHERE id = ? ''' total_changes = bdb._sqlite3.totalchanges() bdb.sql_execute(update_generator_sql, (cmd.name, generator_id)) assert bdb._sqlite3.totalchanges() - total_changes == 1 # Remember the new name for subsequent commands. generator = cmd.name elif isinstance(cmd, ast.AlterGenGeneric): cmds_generic.append(cmd.command) else: assert False, 'Invalid ALTER GENERATOR command: %s' % \ (repr(cmd),) if cmds_generic: modelnos = phrase.modelnos modelnos_invalid = None if modelnos is None else [ modelno for modelno in modelnos if not core.bayesdb_generator_has_model(bdb, generator_id, modelno) ] if modelnos_invalid: raise BQLError(bdb, 'No such models in generator %s: %s' % (repr(phrase.generator), repr(modelnos))) # Call generic alternations on the backend. backend = core.bayesdb_generator_backend(bdb, generator_id) backend.alter(bdb, generator_id, modelnos, cmds_generic) return empty_cursor(bdb) if isinstance(phrase, ast.InitModels): if not core.bayesdb_has_generator(bdb, None, phrase.generator): raise BQLError(bdb, 'No such generator: %s' % (phrase.generator,)) generator_id = core.bayesdb_get_generator(bdb, None, phrase.generator) modelnos = range(phrase.nmodels) with bdb.savepoint(): # Find the model numbers. Omit existing ones for # ifnotexists; reject existing ones otherwise. if phrase.ifnotexists: modelnos = set(modelno for modelno in modelnos if not core.bayesdb_generator_has_model(bdb, generator_id, modelno)) else: existing = set(modelno for modelno in modelnos if core.bayesdb_generator_has_model(bdb, generator_id, modelno)) if 0 < len(existing): raise BQLError(bdb, 'Generator %s already has models: %s' % (repr(phrase.generator), sorted(existing))) # Stop now if there's nothing to initialize. if len(modelnos) == 0: return # Create the bayesdb_generator_model records. modelnos = sorted(modelnos) insert_model_sql = ''' INSERT INTO bayesdb_generator_model (generator_id, modelno) VALUES (:generator_id, :modelno) ''' for modelno in modelnos: bdb.sql_execute(insert_model_sql, { 'generator_id': generator_id, 'modelno': modelno, }) # Do backend-specific initialization. backend = core.bayesdb_generator_backend(bdb, generator_id) backend.initialize_models(bdb, generator_id, modelnos) return empty_cursor(bdb) if isinstance(phrase, ast.AnalyzeModels): # WARNING: It is the backend's responsibility to work in a # transaction. # # WARNING: It is the backend's responsibility to update the # iteration count in bayesdb_generator_model records. # # We do this so that the backend can save incremental # progress in case of ^C in the middle. # # XXX Put these warning somewhere more appropriate. if not core.bayesdb_has_generator(bdb, None, phrase.generator): raise BQLError(bdb, 'No such generator: %s' % (phrase.generator,)) generator_id = core.bayesdb_get_generator(bdb, None, phrase.generator) backend = core.bayesdb_generator_backend(bdb, generator_id) # XXX Should allow parameters for iterations and ckpt/iter. backend.analyze_models(bdb, generator_id, modelnos=phrase.modelnos, iterations=phrase.iterations, max_seconds=phrase.seconds, ckpt_iterations=phrase.ckpt_iterations, ckpt_seconds=phrase.ckpt_seconds, program=phrase.program) return empty_cursor(bdb) if isinstance(phrase, ast.DropModels): with bdb.savepoint(): generator_id = core.bayesdb_get_generator( bdb, None, phrase.generator) backend = core.bayesdb_generator_backend(bdb, generator_id) modelnos = None if phrase.modelnos is not None: lookup_model_sql = ''' SELECT COUNT(*) FROM bayesdb_generator_model WHERE generator_id = :generator_id AND modelno = :modelno ''' modelnos = sorted(list(phrase.modelnos)) for modelno in modelnos: cursor = bdb.sql_execute(lookup_model_sql, { 'generator_id': generator_id, 'modelno': modelno, }) if cursor_value(cursor) == 0: raise BQLError(bdb, 'No such model' ' in generator %s: %s' % (repr(phrase.generator), repr(modelno))) backend.drop_models(bdb, generator_id, modelnos=modelnos) if modelnos is None: drop_models_sql = ''' DELETE FROM bayesdb_generator_model WHERE generator_id = ? ''' bdb.sql_execute(drop_models_sql, (generator_id,)) else: drop_model_sql = ''' DELETE FROM bayesdb_generator_model WHERE generator_id = :generator_id AND modelno = :modelno ''' for modelno in modelnos: bdb.sql_execute(drop_model_sql, { 'generator_id': generator_id, 'modelno': modelno, }) return empty_cursor(bdb) if isinstance(phrase, ast.Regress): # Retrieve the population. if not core.bayesdb_has_population(bdb, phrase.population): raise BQLError(bdb, 'No such population: %r' % (phrase.population,)) population_id = core.bayesdb_get_population(bdb, phrase.population) # Retrieve the generator generator_id = None if phrase.generator: if not core.bayesdb_has_generator(bdb, population_id, phrase.generator): raise BQLError(bdb, 'No such generator: %r' % (phrase.generator,)) generator_id = core.bayesdb_get_generator( bdb, population_id, phrase.generator) # Retrieve the target variable. if not core.bayesdb_has_variable( bdb, population_id, None, phrase.target): raise BQLError(bdb, 'No such variable: %r' % (phrase.target,)) colno_target = core.bayesdb_variable_number( bdb, population_id, None, phrase.target) stattype = core.bayesdb_variable_stattype(bdb, population_id, generator_id, colno_target) if stattype != 'numerical': raise BQLError(bdb, 'Target variable is not numerical: %r' % (phrase.target,)) # Build the given variables. if any(isinstance(col, ast.SelColAll) for col in phrase.givens): # Using * is not allowed to be mixed with other variables. if len(phrase.givens) > 1: raise BQLError(bdb, 'Cannot use (*) with other givens.') colno_givens = core.bayesdb_variable_numbers( bdb, population_id, None) else: if any(isinstance(col, ast.SelColSub) for col in phrase.givens): # Subexpression needs special compiling. out = compiler.Output(n_numpar, nampar_map, bindings) bql_compiler = compiler.BQLCompiler_None() givens = compiler.expand_select_columns( bdb, phrase.givens, True, bql_compiler, out) else: givens = phrase.givens colno_givens = [ core.bayesdb_variable_number( bdb, population_id, None, given.expression.column) for given in givens ] # Build the arguments to bqlfn.bayesdb_simulate. colno_givens_unique = set( colno for colno in colno_givens if colno!= colno_target ) if len(colno_givens_unique) == 0: raise BQLError(bdb, 'No matching given columns.') constraints = [] colnos = [colno_target] + list(colno_givens_unique) nsamp = 100 if phrase.nsamp is None else phrase.nsamp.value.value modelnos = None if phrase.modelnos is None else str(phrase.modelnos) rows = bqlfn.bayesdb_simulate( bdb, population_id, generator_id, modelnos, constraints, colnos, numpredictions=nsamp) # Retrieve the stattypes. stattypes = [ core.bayesdb_variable_stattype( bdb, population_id, generator_id, colno_given) for colno_given in colno_givens_unique ] # Separate the target values from the given values. target_values = [row[0] for row in rows] given_values = [row[1:] for row in rows] given_names = [ core.bayesdb_variable_name(bdb, population_id, generator_id, given) for given in colno_givens_unique ] # Compute the coefficients. The import to regress_ols is here since the # feature depends on pandas + sklearn, so avoid module-wide import. from bayeslite.regress import regress_ols coefficients = regress_ols( target_values, given_values, given_names, stattypes) # Store the results in a winder. temptable = bdb.temp_table_name() qtt = sqlite3_quote_name(temptable) out = compiler.Output(0, {}, {}) out.winder(''' CREATE TEMP TABLE %s (variable TEXT, coefficient REAL); ''' % (qtt,), ()) for variable, coef in coefficients: out.winder(''' INSERT INTO %s VALUES (?, ?) ''' % (qtt), (variable, coef,)) out.write('SELECT * FROM %s ORDER BY variable' % (qtt,)) out.unwinder('DROP TABLE %s' % (qtt,), ()) winders, unwinders = out.getwindings() return execute_wound( bdb, winders, unwinders, out.getvalue(), out.getbindings()) assert False # XXX
def execute_phrase(bdb, phrase, bindings=()): """Execute the BQL AST phrase `phrase` and return a cursor of results.""" if isinstance(phrase, ast.Parametrized): n_numpar = phrase.n_numpar nampar_map = phrase.nampar_map phrase = phrase.phrase assert 0 < n_numpar else: n_numpar = 0 nampar_map = None # Ignore extraneous bindings. XXX Bad idea? if ast.is_query(phrase): # Compile the query in the transaction in case we need to # execute subqueries to determine column lists. Compiling is # a quick tree descent, so this should be fast. out = compiler.Output(n_numpar, nampar_map, bindings) with bdb.savepoint(): compiler.compile_query(bdb, phrase, out) winders, unwinders = out.getwindings() return execute_wound(bdb, winders, unwinders, out.getvalue(), out.getbindings()) if isinstance(phrase, ast.Begin): txn.bayesdb_begin_transaction(bdb) return empty_cursor(bdb) if isinstance(phrase, ast.Rollback): txn.bayesdb_rollback_transaction(bdb) return empty_cursor(bdb) if isinstance(phrase, ast.Commit): txn.bayesdb_commit_transaction(bdb) return empty_cursor(bdb) if isinstance(phrase, ast.CreateTabAs): assert ast.is_query(phrase.query) with bdb.savepoint(): if core.bayesdb_has_table(bdb, phrase.name): if phrase.ifnotexists: return empty_cursor(bdb) else: raise BQLError( bdb, 'Name already defined as table: %s' % (repr(phrase.name), )) out = compiler.Output(n_numpar, nampar_map, bindings) qt = sqlite3_quote_name(phrase.name) temp = 'TEMP ' if phrase.temp else '' ifnotexists = 'IF NOT EXISTS ' if phrase.ifnotexists else '' out.write('CREATE %sTABLE %s%s AS ' % (temp, ifnotexists, qt)) compiler.compile_query(bdb, phrase.query, out) winders, unwinders = out.getwindings() with compiler.bayesdb_wind(bdb, winders, unwinders): bdb.sql_execute(out.getvalue(), out.getbindings()) return empty_cursor(bdb) if isinstance(phrase, ast.CreateTabCsv): with bdb.savepoint(): table_exists = core.bayesdb_has_table(bdb, phrase.name) if table_exists: if phrase.ifnotexists: return empty_cursor(bdb) else: raise BQLError( bdb, 'Table already exists: %s' % (repr(phrase.name), )) bayesdb_read_csv_file(bdb, phrase.name, phrase.csv, header=True, create=True) return empty_cursor(bdb) if isinstance(phrase, ast.DropTab): with bdb.savepoint(): sql = 'SELECT COUNT(*) FROM bayesdb_population WHERE tabname = ?' cursor = bdb.sql_execute(sql, (phrase.name, )) if 0 < cursor_value(cursor): raise BQLError( bdb, 'Table still in use by populations: %s' % (repr(phrase.name), )) bdb.sql_execute('DELETE FROM bayesdb_column WHERE tabname = ?', (phrase.name, )) ifexists = 'IF EXISTS ' if phrase.ifexists else '' qt = sqlite3_quote_name(phrase.name) return bdb.sql_execute('DROP TABLE %s%s' % (ifexists, qt)) if isinstance(phrase, ast.AlterTab): with bdb.savepoint(): table = phrase.table if not core.bayesdb_has_table(bdb, table): raise BQLError(bdb, 'No such table: %s' % (repr(table), )) for cmd in phrase.commands: if isinstance(cmd, ast.AlterTabRenameTab): # If the names differ only in case, we have to do # some extra work because SQLite will reject the # table rename. Note that we may even have table # == cmd.name here, but if the stored table name # differs in case from cmd.name, we want to update # it anyway. if casefold(table) == casefold(cmd.name): # Go via a temporary table. temp = table + '_temp' while core.bayesdb_has_table(bdb, temp): temp += '_temp' rename_table(bdb, table, temp) rename_table(bdb, temp, cmd.name) else: # Make sure nothing else has this name and # rename it. if core.bayesdb_has_table(bdb, cmd.name): raise BQLError( bdb, 'Name already defined as table' ': %s' % (repr(cmd.name), )) rename_table(bdb, table, cmd.name) # Remember the new name for subsequent commands. table = cmd.name elif isinstance(cmd, ast.AlterTabRenameCol): # XXX Need to deal with this in the compiler. raise NotImplementedError('Renaming columns' ' not yet implemented.') # Make sure the old name exist and the new name does not. old_folded = casefold(cmd.old) new_folded = casefold(cmd.new) if old_folded != new_folded: if not core.bayesdb_table_has_column( bdb, table, cmd.old): raise BQLError( bdb, 'No such column in table %s' ': %s' % (repr(table), repr(cmd.old))) if core.bayesdb_table_has_column(bdb, table, cmd.new): raise BQLError( bdb, 'Column already exists' ' in table %s: %s' % (repr(table), repr(cmd.new))) # Update bayesdb_column. Everything else refers # to columns by (tabname, colno) pairs rather than # by names. update_column_sql = ''' UPDATE bayesdb_column SET name = :new WHERE tabname = :table AND name = :old ''' total_changes = bdb._sqlite3.totalchanges() bdb.sql_execute(update_column_sql, { 'table': table, 'old': cmd.old, 'new': cmd.new, }) assert bdb._sqlite3.totalchanges() - total_changes == 1 # ...except metamodels may have the (case-folded) # name cached. if old_folded != new_folded: generators_sql = ''' SELECT id FROM bayesdb_generator WHERE tabname = ? ''' cursor = bdb.sql_execute(generators_sql, (table, )) for (generator_id, ) in cursor: metamodel = core.bayesdb_generator_metamodel( bdb, generator_id) metamodel.rename_column(bdb, generator_id, old_folded, new_folded) else: assert False, 'Invalid alter table command: %s' % \ (cmd,) return empty_cursor(bdb) if isinstance(phrase, ast.GuessSchema): if not core.bayesdb_has_table(bdb, phrase.table): raise BQLError(bdb, 'No such table : %s' % phrase.table) out = compiler.Output(0, {}, {}) with bdb.savepoint(): qt = sqlite3_quote_name(phrase.table) temptable = bdb.temp_table_name() qtt = sqlite3_quote_name(temptable) cursor = bdb.sql_execute('SELECT * FROM %s' % (qt, )) column_names = [d[0] for d in cursor.description] rows = cursor.fetchall() stattypes = bayesdb_guess_stattypes(column_names, rows) distinct_value_counts = [ len(set([row[i] for row in rows])) for i in range(len(column_names)) ] out.winder( ''' CREATE TEMP TABLE %s (column TEXT, stattype TEXT, num_distinct INTEGER, reason TEXT) ''' % (qtt), ()) for cn, st, ct in zip(column_names, stattypes, distinct_value_counts): out.winder( ''' INSERT INTO %s VALUES (?, ?, ?, ?) ''' % (qtt), (cn, st[0], ct, st[1])) out.write('SELECT * FROM %s' % (qtt, )) out.unwinder('DROP TABLE %s' % (qtt, ), ()) winders, unwinders = out.getwindings() return execute_wound(bdb, winders, unwinders, out.getvalue(), out.getbindings()) if isinstance(phrase, ast.CreatePop): with bdb.savepoint(): _create_population(bdb, phrase) return empty_cursor(bdb) if isinstance(phrase, ast.DropPop): with bdb.savepoint(): if not core.bayesdb_has_population(bdb, phrase.name): if phrase.ifexists: return empty_cursor(bdb) raise BQLError(bdb, 'No such population: %r' % (phrase.name, )) population_id = core.bayesdb_get_population(bdb, phrase.name) generator_ids = core.bayesdb_population_generators( bdb, population_id) if generator_ids: generators = [ core.bayesdb_generator_name(bdb, gid) for gid in generator_ids ] raise BQLError( bdb, 'Population %r still has metamodels: %r' % (phrase.name, generators)) # XXX helpful error checking if generators still exist # XXX check change counts bdb.sql_execute( ''' DELETE FROM bayesdb_variable WHERE population_id = ? ''', (population_id, )) bdb.sql_execute( ''' DELETE FROM bayesdb_population WHERE id = ? ''', (population_id, )) return empty_cursor(bdb) if isinstance(phrase, ast.AlterPop): with bdb.savepoint(): population = phrase.population if not core.bayesdb_has_population(bdb, population): raise BQLError(bdb, 'No such population: %s' % (repr(population), )) population_id = core.bayesdb_get_population(bdb, population) for cmd in phrase.commands: if isinstance(cmd, ast.AlterPopAddVar): # Ensure column exists in base table. table = core.bayesdb_population_table(bdb, population_id) if not core.bayesdb_table_has_column(bdb, table, cmd.name): raise BQLError( bdb, 'No such variable in base table: %s' % (cmd.name)) # Ensure variable not already in population. if core.bayesdb_has_variable(bdb, population_id, None, cmd.name): raise BQLError( bdb, 'Variable already in population: %s' % (cmd.name)) # Ensure there is at least observation in the column. qt = sqlite3_quote_name(table) qc = sqlite3_quote_name(cmd.name) cursor = bdb.sql_execute( 'SELECT COUNT(*) FROM %s WHERE %s IS NOT NULL' % (qt, qc)) if cursor_value(cursor) == 0: raise BQLError( bdb, 'Cannot add variable without any values: %s' % (cmd.name)) # If stattype is None, guess. if cmd.stattype is None: cursor = bdb.sql_execute('SELECT %s FROM %s' % (qc, qt)) rows = cursor.fetchall() [stattype, reason] = bayesdb_guess_stattypes([cmd.name], rows)[0] # Fail if trying to model a key. if stattype == 'key': raise BQLError( bdb, 'Values in column %s appear to be keys.' % (cmd.name, )) # Fail if cannot determine a stattype. elif stattype == 'ignore': raise BQLError( bdb, 'Failed to determine a stattype for %s, ' 'please specify one manually.' % (cmd.name, )) # If user specified stattype, ensure it exists. elif not core.bayesdb_has_stattype(bdb, cmd.stattype): raise BQLError(bdb, 'Invalid stattype: %s' % (cmd.stattype)) else: stattype = cmd.stattype # Check that strings are not being modeled as numerical. if stattype == 'numerical' \ and _column_contains_string(bdb, table, cmd.name): raise BQLError( bdb, 'Numerical column contains string values: %r ' % (qc, )) with bdb.savepoint(): # Add the variable to the population. core.bayesdb_add_variable(bdb, population_id, cmd.name, stattype) colno = core.bayesdb_variable_number( bdb, population_id, None, cmd.name) # Add the variable to each (initialized) metamodel in # the population. generator_ids = filter( lambda g: core.bayesdb_generator_modelnos(bdb, g), core.bayesdb_population_generators( bdb, population_id), ) for generator_id in generator_ids: # XXX Omit needless bayesdb_generator_column table # Github issue #441. bdb.sql_execute( ''' INSERT INTO bayesdb_generator_column VALUES (:generator_id, :colno, :stattype) ''', { 'generator_id': generator_id, 'colno': colno, 'stattype': stattype, }) metamodel = core.bayesdb_generator_metamodel( bdb, generator_id) metamodel.add_column(bdb, generator_id, colno) elif isinstance(cmd, ast.AlterPopStatType): # Check the no metamodels are defined for this population. generators = core.bayesdb_population_generators( bdb, population_id) if generators: raise BQLError( bdb, 'Cannot update statistical types for population ' '%s, it has metamodels: %s' % ( repr(population), repr(generators), )) # Check all the variables are in the population. unknown = [ c for c in cmd.names if not core.bayesdb_has_variable( bdb, population_id, None, c) ] if unknown: raise BQLError( bdb, 'No such variables in population: %s' % (repr(unknown))) # Check the statistical type is valid. if not core.bayesdb_has_stattype(bdb, cmd.stattype): raise BQLError( bdb, 'Invalid statistical type: %r' % (repr(cmd.stattype), )) # Check that strings are not being modeled as numerical. if cmd.stattype == 'numerical': table = core.bayesdb_population_table( bdb, population_id) numerical_string_vars = [ col for col in cmd.names if _column_contains_string(bdb, table, col) ] if numerical_string_vars: raise BQLError( bdb, 'Columns with string values modeled as ' 'numerical: %r' % (numerical_string_vars, )) # Perform the stattype update. colnos = [ core.bayesdb_variable_number(bdb, population_id, None, c) for c in cmd.names ] qcolnos = ','.join('%d' % (colno, ) for colno in colnos) update_stattype_sql = ''' UPDATE bayesdb_variable SET stattype = ? WHERE population_id = ? AND colno IN (%s) ''' % (qcolnos, ) bdb.sql_execute(update_stattype_sql, ( casefold(cmd.stattype), population_id, )) else: assert False, 'Invalid ALTER POPULATION command: %s' % \ (repr(cmd),) return empty_cursor(bdb) if isinstance(phrase, ast.CreateGen): # Find the population. if not core.bayesdb_has_population(bdb, phrase.population): raise BQLError(bdb, 'No such population: %r' % (phrase.population, )) population_id = core.bayesdb_get_population(bdb, phrase.population) table = core.bayesdb_population_table(bdb, population_id) # Find the metamodel, or use the default. metamodel_name = phrase.metamodel if phrase.metamodel is None: metamodel_name = 'cgpm' if metamodel_name not in bdb.metamodels: raise BQLError(bdb, 'No such metamodel: %s' % (repr(metamodel_name), )) metamodel = bdb.metamodels[metamodel_name] with bdb.savepoint(): if core.bayesdb_has_generator(bdb, population_id, phrase.name): if not phrase.ifnotexists: raise BQLError( bdb, 'Name already defined as generator: %s' % (repr(phrase.name), )) else: # Insert a record into bayesdb_generator and get the # assigned id. bdb.sql_execute( ''' INSERT INTO bayesdb_generator (name, tabname, population_id, metamodel) VALUES (?, ?, ?, ?) ''', (phrase.name, table, population_id, metamodel.name())) generator_id = core.bayesdb_get_generator( bdb, population_id, phrase.name) # Populate bayesdb_generator_column. # # XXX Omit needless bayesdb_generator_column table -- # Github issue #441. bdb.sql_execute( ''' INSERT INTO bayesdb_generator_column (generator_id, colno, stattype) SELECT :generator_id, colno, stattype FROM bayesdb_variable WHERE population_id = :population_id AND generator_id IS NULL ''', { 'generator_id': generator_id, 'population_id': population_id, }) # Do any metamodel-specific initialization. metamodel.create_generator(bdb, generator_id, phrase.schema, baseline=phrase.baseline) # Populate bayesdb_generator_column with any latent # variables that metamodel.create_generator has added # with bayesdb_add_latent. bdb.sql_execute( ''' INSERT INTO bayesdb_generator_column (generator_id, colno, stattype) SELECT :generator_id, colno, stattype FROM bayesdb_variable WHERE population_id = :population_id AND generator_id = :generator_id ''', { 'generator_id': generator_id, 'population_id': population_id, }) # All done. Nothing to return. return empty_cursor(bdb) if isinstance(phrase, ast.DropGen): with bdb.savepoint(): if not core.bayesdb_has_generator(bdb, None, phrase.name): if phrase.ifexists: return empty_cursor(bdb) raise BQLError(bdb, 'No such generator: %s' % (repr(phrase.name), )) generator_id = core.bayesdb_get_generator(bdb, None, phrase.name) metamodel = core.bayesdb_generator_metamodel(bdb, generator_id) # Metamodel-specific destruction. metamodel.drop_generator(bdb, generator_id) # Drop the columns, models, and, finally, generator. drop_columns_sql = ''' DELETE FROM bayesdb_generator_column WHERE generator_id = ? ''' bdb.sql_execute(drop_columns_sql, (generator_id, )) drop_model_sql = ''' DELETE FROM bayesdb_generator_model WHERE generator_id = ? ''' bdb.sql_execute(drop_model_sql, (generator_id, )) drop_generator_sql = ''' DELETE FROM bayesdb_generator WHERE id = ? ''' bdb.sql_execute(drop_generator_sql, (generator_id, )) return empty_cursor(bdb) if isinstance(phrase, ast.AlterGen): with bdb.savepoint(): generator = phrase.generator if not core.bayesdb_has_generator(bdb, None, generator): raise BQLError(bdb, 'No such generator: %s' % (repr(generator), )) generator_id = core.bayesdb_get_generator(bdb, None, generator) cmds_generic = [] for cmd in phrase.commands: if isinstance(cmd, ast.AlterGenRenameGen): # Disable modelnos with AlterGenRenameGen. if phrase.modelnos is not None: raise BQLError(bdb, 'Cannot specify models for RENAME') # Make sure nothing else has this name. if casefold(generator) != casefold(cmd.name): if core.bayesdb_has_table(bdb, cmd.name): raise BQLError( bdb, 'Name already defined as table' ': %s' % (repr(cmd.name), )) if core.bayesdb_has_generator(bdb, None, cmd.name): raise BQLError( bdb, 'Name already defined' ' as generator: %s' % (repr(cmd.name), )) # Update bayesdb_generator. Everything else # refers to it by id. update_generator_sql = ''' UPDATE bayesdb_generator SET name = ? WHERE id = ? ''' total_changes = bdb._sqlite3.totalchanges() bdb.sql_execute(update_generator_sql, (cmd.name, generator_id)) assert bdb._sqlite3.totalchanges() - total_changes == 1 # Remember the new name for subsequent commands. generator = cmd.name elif isinstance(cmd, ast.AlterGenGeneric): cmds_generic.append(cmd.command) else: assert False, 'Invalid ALTER GENERATOR command: %s' % \ (repr(cmd),) if cmds_generic: modelnos = phrase.modelnos modelnos_invalid = None if modelnos is None else [ modelno for modelno in modelnos if not core.bayesdb_generator_has_model( bdb, generator_id, modelno) ] if modelnos_invalid: raise BQLError( bdb, 'No such models in generator %s: %s' % (repr(phrase.generator), repr(modelnos))) # Call generic alternations on the metamodel. metamodel = core.bayesdb_generator_metamodel(bdb, generator_id) metamodel.alter(bdb, generator_id, modelnos, cmds_generic) return empty_cursor(bdb) if isinstance(phrase, ast.InitModels): if not core.bayesdb_has_generator(bdb, None, phrase.generator): raise BQLError(bdb, 'No such generator: %s' % (phrase.generator, )) generator_id = core.bayesdb_get_generator(bdb, None, phrase.generator) modelnos = range(phrase.nmodels) with bdb.savepoint(): # Find the model numbers. Omit existing ones for # ifnotexists; reject existing ones otherwise. if phrase.ifnotexists: modelnos = set(modelno for modelno in modelnos if not core.bayesdb_generator_has_model( bdb, generator_id, modelno)) else: existing = set(modelno for modelno in modelnos if core.bayesdb_generator_has_model( bdb, generator_id, modelno)) if 0 < len(existing): raise BQLError( bdb, 'Generator %s already has models: %s' % (repr(phrase.generator), sorted(existing))) # Stop now if there's nothing to initialize. if len(modelnos) == 0: return # Create the bayesdb_generator_model records. modelnos = sorted(modelnos) insert_model_sql = ''' INSERT INTO bayesdb_generator_model (generator_id, modelno, iterations) VALUES (:generator_id, :modelno, :iterations) ''' for modelno in modelnos: bdb.sql_execute( insert_model_sql, { 'generator_id': generator_id, 'modelno': modelno, 'iterations': 0, }) # Do metamodel-specific initialization. metamodel = core.bayesdb_generator_metamodel(bdb, generator_id) metamodel.initialize_models(bdb, generator_id, modelnos) return empty_cursor(bdb) if isinstance(phrase, ast.AnalyzeModels): if not phrase.wait: raise NotImplementedError('No background analysis -- use WAIT.') # WARNING: It is the metamodel's responsibility to work in a # transaction. # # WARNING: It is the metamodel's responsibility to update the # iteration count in bayesdb_generator_model records. # # We do this so that the metamodel can save incremental # progress in case of ^C in the middle. # # XXX Put these warning somewhere more appropriate. if not core.bayesdb_has_generator(bdb, None, phrase.generator): raise BQLError(bdb, 'No such generator: %s' % (phrase.generator, )) generator_id = core.bayesdb_get_generator(bdb, None, phrase.generator) metamodel = core.bayesdb_generator_metamodel(bdb, generator_id) # XXX Should allow parameters for iterations and ckpt/iter. metamodel.analyze_models(bdb, generator_id, modelnos=phrase.modelnos, iterations=phrase.iterations, max_seconds=phrase.seconds, ckpt_iterations=phrase.ckpt_iterations, ckpt_seconds=phrase.ckpt_seconds, program=phrase.program) return empty_cursor(bdb) if isinstance(phrase, ast.DropModels): with bdb.savepoint(): generator_id = core.bayesdb_get_generator(bdb, None, phrase.generator) metamodel = core.bayesdb_generator_metamodel(bdb, generator_id) modelnos = None if phrase.modelnos is not None: lookup_model_sql = ''' SELECT COUNT(*) FROM bayesdb_generator_model WHERE generator_id = :generator_id AND modelno = :modelno ''' modelnos = sorted(list(phrase.modelnos)) for modelno in modelnos: cursor = bdb.sql_execute(lookup_model_sql, { 'generator_id': generator_id, 'modelno': modelno, }) if cursor_value(cursor) == 0: raise BQLError( bdb, 'No such model' ' in generator %s: %s' % (repr(phrase.generator), repr(modelno))) metamodel.drop_models(bdb, generator_id, modelnos=modelnos) if modelnos is None: drop_models_sql = ''' DELETE FROM bayesdb_generator_model WHERE generator_id = ? ''' bdb.sql_execute(drop_models_sql, (generator_id, )) else: drop_model_sql = ''' DELETE FROM bayesdb_generator_model WHERE generator_id = :generator_id AND modelno = :modelno ''' for modelno in modelnos: bdb.sql_execute(drop_model_sql, { 'generator_id': generator_id, 'modelno': modelno, }) return empty_cursor(bdb) if isinstance(phrase, ast.Regress): # Retrieve the population. if not core.bayesdb_has_population(bdb, phrase.population): raise BQLError(bdb, 'No such population: %r' % (phrase.population, )) population_id = core.bayesdb_get_population(bdb, phrase.population) # Retrieve the metamodel. generator_id = None if phrase.metamodel: if not core.bayesdb_has_generator(bdb, population_id, phrase.metamodel): raise BQLError(bdb, 'No such metamodel: %r' % (phrase.population, )) generator_id = core.bayesdb_get_generator(bdb, population_id, phrase.metamodel) # Retrieve the target variable. if not core.bayesdb_has_variable(bdb, population_id, None, phrase.target): raise BQLError(bdb, 'No such variable: %r' % (phrase.target, )) colno_target = core.bayesdb_variable_number(bdb, population_id, None, phrase.target) if core.bayesdb_variable_stattype(bdb, population_id, colno_target) != \ 'numerical': raise BQLError( bdb, 'Target variable is not numerical: %r' % (phrase.target, )) # Build the given variables. if any(isinstance(col, ast.SelColAll) for col in phrase.givens): # Using * is not allowed to be mixed with other variables. if len(phrase.givens) > 1: raise BQLError(bdb, 'Cannot use (*) with other givens.') colno_givens = core.bayesdb_variable_numbers( bdb, population_id, None) else: if any(isinstance(col, ast.SelColSub) for col in phrase.givens): # Subexpression needs special compiling. out = compiler.Output(n_numpar, nampar_map, bindings) bql_compiler = compiler.BQLCompiler_None() givens = compiler.expand_select_columns( bdb, phrase.givens, True, bql_compiler, out) else: givens = phrase.givens colno_givens = [ core.bayesdb_variable_number(bdb, population_id, None, given.expression.column) for given in givens ] # Build the arguments to bqlfn.bayesdb_simulate. colno_givens_unique = set(colno for colno in colno_givens if colno != colno_target) if len(colno_givens_unique) == 0: raise BQLError(bdb, 'No matching given columns.') constraints = [] colnos = [colno_target] + list(colno_givens_unique) nsamp = 100 if phrase.nsamp is None else phrase.nsamp.value.value modelnos = None if phrase.modelnos is None else str(phrase.modelnos) rows = bqlfn.bayesdb_simulate(bdb, population_id, generator_id, modelnos, constraints, colnos, numpredictions=nsamp) # Retrieve the stattypes. stattypes = [ core.bayesdb_variable_stattype(bdb, population_id, colno_given) for colno_given in colno_givens_unique ] # Separate the target values from the given values. target_values = [row[0] for row in rows] given_values = [row[1:] for row in rows] given_names = [ core.bayesdb_variable_name(bdb, population_id, given) for given in colno_givens_unique ] # Compute the coefficients. The import to regress_ols is here since the # feature depends on pandas + sklearn, so avoid module-wide import. from bayeslite.regress import regress_ols coefficients = regress_ols(target_values, given_values, given_names, stattypes) # Store the results in a winder. temptable = bdb.temp_table_name() qtt = sqlite3_quote_name(temptable) out = compiler.Output(0, {}, {}) out.winder( ''' CREATE TEMP TABLE %s (variable TEXT, coefficient REAL); ''' % (qtt, ), ()) for variable, coef in coefficients: out.winder( ''' INSERT INTO %s VALUES (?, ?) ''' % (qtt), ( variable, coef, )) out.write('SELECT * FROM %s ORDER BY variable' % (qtt, )) out.unwinder('DROP TABLE %s' % (qtt, ), ()) winders, unwinders = out.getwindings() return execute_wound(bdb, winders, unwinders, out.getvalue(), out.getbindings()) assert False # XXX
def retrieve_variable(var): if not core.bayesdb_has_variable( bdb, population_id, generator_id, var): raise BQLError(bdb, 'No such population variable: %s' % (var,)) return core.bayesdb_variable_number( bdb, population_id, generator_id, var)