<commit_before>Scholar.Search = function(){
	this._sql = null;
	this._sqlParams = null;
	this._maxSearchConditionID = 0;
	this._conditions = [];
	this._savedSearchID = null;
	this._savedSearchName = null;
}


/*
 * Set the name for the saved search
 *
 * Must be called before save() for new searches
 */
Scholar.Search.prototype.setName = function(name){
	if (!name){
		throw("Invalid saved search name '" + name + '"');
	}
	
	this._savedSearchName = name;
}


/*
 * Load a saved search from the DB
 */
Scholar.Search.prototype.load = function(savedSearchID){
	var sql = "SELECT savedSearchName, MAX(searchConditionID) AS maxID "
		+ "FROM savedSearches NATURAL JOIN savedSearchConditions "
		+ "WHERE savedSearchID=" + savedSearchID + " GROUP BY savedSearchID";
	var row = Scholar.DB.rowQuery(sql);
	
	if (!row){
		throw('Saved search ' + savedSearchID + ' does not exist');
	}
	
	this._sql = null;
	this._sqlParams = null;
	this._maxSearchConditionID = row['maxID'];
	this._conditions = [];
	this._savedSearchID = savedSearchID;
	this._savedSearchName = row['savedSearchName'];
	
	var conditions = Scholar.DB.query("SELECT * FROM savedSearchConditions "
		+ "WHERE savedSearchID=" + savedSearchID + " ORDER BY searchConditionID");
	
	for (var i in conditions){
		this._conditions[conditions[i]['searchConditionID']] = {
			condition: conditions[i]['condition'],
			operator: conditions[i]['operator'],
			value: conditions[i]['value']
		};
	}
}


/*
 * Save the search to the DB and return a savedSearchID
 *
 * For new searches, setName() must be called before saving
 */
Scholar.Search.prototype.save = function(){
	if (!this._savedSearchName){
		throw('Name not provided for saved search');
	}
	
	Scholar.DB.beginTransaction();
	
	if (this._savedSearchID){
		var sql = "UPDATE savedSearches SET savedSearchName=? WHERE savedSearchID=?";
		Scholar.DB.query(sql, [this._savedSearchName, this._savedSearchID]);
		
		Scholar.DB.query("DELETE FROM savedSearchConditions "
			+ "WHERE savedSearchID=" + this._savedSearchID);
	}
	else {
		this._savedSearchID
			= Scholar.getRandomID('savedSearches', 'savedSearchID');
		
		var sql = "INSERT INTO savedSearches (savedSearchID, savedSearchName) "
			+ "VALUES (?,?)";
		Scholar.DB.query(sql,
			[this._savedSearchID, {string: this._savedSearchName}]);
	}
	
	// TODO: use proper bound parameters once DB class is updated
	for (var i in this._conditions){
		var sql = "INSERT INTO savedSearchConditions (savedSearchID, "
			+ "searchConditionID, condition, operator, value) VALUES (?,?,?,?,?)";
		
		var sqlParams = [this._savedSearchID, i, this._conditions[i]['condition'],
			this._conditions[i]['operator']
				? this._conditions[i]['operator'] : null,
			this._conditions[i]['value']
				? this._conditions[i]['value'] : null];
		Scholar.DB.query(sql, sqlParams);
	}
	
	Scholar.DB.commitTransaction();
	
	return this._savedSearchID;
}


Scholar.Search.prototype.addCondition = function(condition, operator, value){
	if (!Scholar.SearchConditions.hasOperator(condition, operator)){
		throw ("Invalid operator '" + operator + "' for condition " + condition);
	}
	
	var searchConditionID = this._maxSearchConditionID++;
	
	this._conditions[searchConditionID] = {
		condition: condition,
		operator: operator,
		value: value
	};
	
	this._sql = null;
	this._sqlParams = null;
	return searchConditionID;
}


Scholar.Search.prototype.updateCondition = function(searchConditionID, condition, operator, value){
	if (typeof this._conditions[searchConditionID] == 'undefined'){
		throw ('Invalid searchConditionID ' + searchConditionID + ' in updateCondition()');
	}
	
	if (!Scholar.SearchConditions.hasOperator(condition, operator)){
		throw ("Invalid operator '" + operator + "' for condition " + condition);
	}
	
	this._conditions[searchConditionID] = {
		condition: condition,
		operator: operator,
		value: value
	};
	
	this._sql = null;
	this._sqlParams = null;
}


Scholar.Search.prototype.removeCondition = function(searchConditionID){
	if (typeof this._conditions[searchConditionID] == 'undefined'){
		throw ('Invalid searchConditionID ' + searchConditionID + ' in removeCondition()');
	}
	
	delete this._conditions[searchConditionID];
	
	var i = searchConditionID + 1;
	while (typeof this._conditions[i] != 'undefined'){
		this._conditions[i-1] = this._conditions[i];
		delete this._conditions[i];
		i++;
	}
	
	this._maxSearchConditionID--;
}


/*
 * Returns an array with 'condition', 'operator', and 'value'
 * for the given searchConditionID
 */
Scholar.Search.prototype.getSearchCondition = function(searchConditionID){
	return this._conditions[searchConditionID];
}


/*
 * Returns a multidimensional array of conditions/operator/value sets
 * used in the search, indexed by searchConditionID
 */
Scholar.Search.prototype.getSearchConditions = function(){
	// TODO: make copy
	return this._conditions;<commit_msg>Make searchConditionIDs a little easier to work with--now accessible by with the .id property of search conditions in addition to index<commit_after>Scholar.Search = function(){
	this._sql = null;
	this._sqlParams = null;
	this._maxSearchConditionID = 0;
	this._conditions = [];
	this._savedSearchID = null;
	this._savedSearchName = null;
}


/*
 * Set the name for the saved search
 *
 * Must be called before save() for new searches
 */
Scholar.Search.prototype.setName = function(name){
	if (!name){
		throw("Invalid saved search name '" + name + '"');
	}
	
	this._savedSearchName = name;
}


/*
 * Load a saved search from the DB
 */
Scholar.Search.prototype.load = function(savedSearchID){
	var sql = "SELECT savedSearchName, MAX(searchConditionID) AS maxID "
		+ "FROM savedSearches NATURAL JOIN savedSearchConditions "
		+ "WHERE savedSearchID=" + savedSearchID + " GROUP BY savedSearchID";
	var row = Scholar.DB.rowQuery(sql);
	
	if (!row){
		throw('Saved search ' + savedSearchID + ' does not exist');
	}
	
	this._sql = null;
	this._sqlParams = null;
	this._maxSearchConditionID = row['maxID'];
	this._conditions = [];
	this._savedSearchID = savedSearchID;
	this._savedSearchName = row['savedSearchName'];
	
	var conditions = Scholar.DB.query("SELECT * FROM savedSearchConditions "
		+ "WHERE savedSearchID=" + savedSearchID + " ORDER BY searchConditionID");
	
	for (var i in conditions){
		this._conditions[conditions[i]['searchConditionID']] = {
			id: conditions[i]['searchConditionID'],
			condition: conditions[i]['condition'],
			operator: conditions[i]['operator'],
			value: conditions[i]['value']
		};
	}
}


/*
 * Save the search to the DB and return a savedSearchID
 *
 * For new searches, setName() must be called before saving
 */
Scholar.Search.prototype.save = function(){
	if (!this._savedSearchName){
		throw('Name not provided for saved search');
	}
	
	Scholar.DB.beginTransaction();
	
	if (this._savedSearchID){
		var sql = "UPDATE savedSearches SET savedSearchName=? WHERE savedSearchID=?";
		Scholar.DB.query(sql, [this._savedSearchName, this._savedSearchID]);
		
		Scholar.DB.query("DELETE FROM savedSearchConditions "
			+ "WHERE savedSearchID=" + this._savedSearchID);
	}
	else {
		this._savedSearchID
			= Scholar.getRandomID('savedSearches', 'savedSearchID');
		
		var sql = "INSERT INTO savedSearches (savedSearchID, savedSearchName) "
			+ "VALUES (?,?)";
		Scholar.DB.query(sql,
			[this._savedSearchID, {string: this._savedSearchName}]);
	}
	
	// TODO: use proper bound parameters once DB class is updated
	for (var i in this._conditions){
		var sql = "INSERT INTO savedSearchConditions (savedSearchID, "
			+ "searchConditionID, condition, operator, value) VALUES (?,?,?,?,?)";
		
		var sqlParams = [this._savedSearchID, i, this._conditions[i]['condition'],
			this._conditions[i]['operator']
				? this._conditions[i]['operator'] : null,
			this._conditions[i]['value']
				? this._conditions[i]['value'] : null];
		Scholar.DB.query(sql, sqlParams);
	}
	
	Scholar.DB.commitTransaction();
	
	return this._savedSearchID;
}


Scholar.Search.prototype.addCondition = function(condition, operator, value){
	if (!Scholar.SearchConditions.hasOperator(condition, operator)){
		throw ("Invalid operator '" + operator + "' for condition " + condition);
	}
	
	var searchConditionID = this._maxSearchConditionID++;
	
	this._conditions[searchConditionID] = {
		id: searchConditionID,
		condition: condition,
		operator: operator,
		value: value
	};
	
	this._sql = null;
	this._sqlParams = null;
	return searchConditionID;
}


Scholar.Search.prototype.updateCondition = function(searchConditionID, condition, operator, value){
	if (typeof this._conditions[searchConditionID] == 'undefined'){
		throw ('Invalid searchConditionID ' + searchConditionID + ' in updateCondition()');
	}
	
	if (!Scholar.SearchConditions.hasOperator(condition, operator)){
		throw ("Invalid operator '" + operator + "' for condition " + condition);
	}
	
	this._conditions[searchConditionID] = {
		id: searchConditionID,
		condition: condition,
		operator: operator,
		value: value
	};
	
	this._sql = null;
	this._sqlParams = null;
}


Scholar.Search.prototype.removeCondition = function(searchConditionID){
	if (typeof this._conditions[searchConditionID] == 'undefined'){
		throw ('Invalid searchConditionID ' + searchConditionID + ' in removeCondition()');
	}
	
	delete this._conditions[searchConditionID];
	
	var i = searchConditionID + 1;
	while (typeof this._conditions[i] != 'undefined'){
		this._conditions[i-1] = this._conditions[i];
		this._conditions[i-1]['id']--;
		delete this._conditions[i];
		i++;
	}
	
	this._maxSearchConditionID--;
}


/*
 * Returns an array with 'condition', 'operator', and 'value'
 * for the given searchConditionID
 */
Scholar.Search.prototype.getSearchCondition = function(searchConditionID){
	return this._conditions[searchConditionID];
}


/*
 * Returns a multidimensional array of conditions/operator/value sets
 * used in the search, indexed by searchConditionID
 */
Scholar.Search.prototype.getSearchConditions = function(){
	// TODO: make copy
	return this._conditions;
{'fraction_non_alphanumeric': 0.1330394710478862, 'fraction_numerical': 0.0007012622720897616, 'mean_word_length': 4.743958573072497, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '972934', 'n_tokens_mistral': 3206, 'n_tokens_neox': 2935, 'n_words': 724}