docs: def search(self, criterion, table, columns='', fetch=False, radius=1/60., use_converters=False, sql_search=False):
          """
          General search method for tables. For (ra,dec) input in decimal degrees,
          i.e. (12.3456,-65.4321), returns all sources within 1 arcminute, or the specified radius.
          For string input, i.e. 'vb10', returns all sources with case-insensitive partial text
          matches in columns with 'TEXT' data type. For integer input, i.e. 123, returns all
          exact matches of columns with INTEGER data type.
  
          Parameters
          ----------
          criterion: (str, int, sequence, tuple)
              The text, integer, coordinate tuple, or sequence thereof to search the table with.
          table: str
              The name of the table to search
          columns: sequence
              Specific column names to search, otherwise searches all columns
          fetch: bool
              Return the results of the query as an Astropy table
          radius: float
              Radius in degrees in which to search for objects if using (ra,dec). Default: 1/60 degree
          use_converters: bool
              Apply converters to columns with custom data types
          sql_search: bool
              Perform the search by coordinates in a box defined within the SQL commands, rather than with true angular
              separations. Faster, but not a true radial search.
          """
  
          # Get list of columns to search and format properly
          t = self.query("PRAGMA table_info({})".format(table), unpack=True, fmt='table')
          all_columns = t['name'].tolist()
          types = t['type'].tolist()
          columns = columns or all_columns
          columns = np.asarray([columns] if isinstance(columns, str) else columns)
  
          # Separate good and bad columns and corresponding types
          badcols = columns[~np.in1d(columns, all_columns)]
          columns = columns[np.in1d(columns, all_columns)]
          columns = np.array([c for c in all_columns if c in columns])
          types = np.array([t for c, t in zip(all_columns, types) if c in columns])[np.in1d(columns, all_columns)]
          for col in badcols:
              print("'{}' is not a column in the {} table.".format(col, table.upper()))
  
          # Coordinate search
          if sys.version_info[0] == 2:
              str_check = (str, unicode)
          else:
              str_check = str
  
          results = ''
  
          if isinstance(criterion, (tuple, list, np.ndarray)):
              try:
                  if sql_search:
                      q = "SELECT * FROM {} WHERE ra BETWEEN ".format(table) \
                          + str(criterion[0] - radius) + " AND " \
                          + str(criterion[0] + radius) + " AND dec BETWEEN " \
                          + str(criterion[1] - radius) + " AND " \
                          + str(criterion[1] + radius)
                      results = self.query(q, fmt='table')
                  else:
                      t = self.query('SELECT id,ra,dec FROM sources', fmt='table')
                      df = t.to_pandas()
                      df[['ra', 'dec']] = df[['ra', 'dec']].apply(pd.to_numeric)  # convert everything to floats
                      mask = df['ra'].isnull()
                      df = df[~mask]
  
                      df['theta'] = df.apply(ang_sep, axis=1, args=(criterion[0], criterion[1]))
                      good = df['theta'] <= radius
  
                      if sum(good) > 0:
                          params = ", ".join(['{}'.format(s) for s in df[good]['id'].tolist()])
                          try:
                              results = self.query('SELECT * FROM {} WHERE source_id IN ({})'.format(table, params),
                                                   fmt='table')
                          except:
                              results = self.query('SELECT * FROM {} WHERE id IN ({})'.format(table, params),
                                                   fmt='table')
              except:
                  print("Could not search {} table by coordinates {}. Try again.".format(table.upper(), criterion))
  
          # Text string search of columns with 'TEXT' data type
          elif isinstance(criterion, str_check) and any(columns) and 'TEXT' in types:
              try:
                  q = "SELECT * FROM {} WHERE {}".format(table, ' OR '.join([r"REPLACE(" + c + r",' ','') like '%" \
                       + criterion.replace(' ', '') + r"%'" for c, t in zip(columns,types[np.in1d(columns, all_columns)]) \
                       if t == 'TEXT']))
                  results = self.query(q, fmt='table', use_converters=use_converters)
              except:
                  print("Could not search {} table by string {}. Try again.".format(table.upper(), criterion))
  
          # Integer search of columns with 'INTEGER' data type
          elif isinstance(criterion, int):
              try:
                  q = "SELECT * FROM {} WHERE {}".format(table, ' OR '.join(['{}={}'.format(c, criterion) \
                       for c, t in zip(columns, types[np.in1d(columns, all_columns)]) if t == 'INTEGER']))
                  results = self.query(q, fmt='table', use_converters=use_converters)
              except:
                  print("Could not search {} table by id {}. Try again.".format(table.upper(), criterion))
  
          # Problem!
          else:
              print("Could not search {} table by '{}'. Try again.".format(table.upper(), criterion))
  
          # Print or return the results
          if fetch:
              return results or at.Table(names=columns, dtype=[type_dict[t] for t in types], masked=True)
          else:
              if results: 
                  pprint(results, title=table.upper())
              else:
                  print("No results found for {} in the {} table.".format(criterion, table.upper()))