<commit_before># This file is part of NIT ( http://www.nitlanguage.org ).
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License

# Model for the data of Opportunity
module opportunity_model

import sqlite3
import sha1
import serialization

# A SQLiteDB object for `Opportunity`
class OpportunityDB
	super Sqlite3DB

	init open(x) do
		super

		create_db
	end

	# Creates the tables and triggers for Opportunity (SQLite3 DB)
	fun create_db do
		assert create_table("IF NOT EXISTS meetups (id CHAR(40) PRIMARY KEY, name TEXT, date TEXT, place TEXT, answer_mode INTEGER DEFAULT 0);") else
			print error or else "?"
		end
		assert create_table("IF NOT EXISTS people(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, surname TEXT);") else
			print error or else "?"
		end
		assert create_table("IF NOT EXISTS answers(id INTEGER PRIMARY KEY AUTOINCREMENT, meetup_id CHAR(40), name TEXT, FOREIGN KEY(meetup_id) REFERENCES meetups(id));") else
			print error or else "?"
		end
		assert create_table("IF NOT EXISTS part_answers(id_part INTEGER, id_ans INTEGER, value INTEGER, FOREIGN KEY(id_part) REFERENCES people(id), FOREIGN KEY(id_ans) REFERENCES answers(id));") else
			print error or else "?"
		end
		#NOTE: The following triggers could be replaced by ON DELETE CASCADE clauses
		# Thing is, SQLite does not seem to support those operations (well, not by default, it seems
		# we must re-compile the lib to support it. So, well, let's just create triggers heh.
		assert execute("CREATE TRIGGER IF NOT EXISTS answers_clean AFTER DELETE ON meetups BEGIN DELETE FROM answers WHERE answers.meetup_id=OLD.id;END;") else
			print error or else "?"
		end
		assert execute("CREATE TRIGGER IF NOT EXISTS ans_clean AFTER DELETE ON answers BEGIN DELETE FROM part_answers WHERE OLD.id=part_answers.id_ans;END;") else
			print error or else "?"
		end
		assert execute("CREATE TRIGGER IF NOT EXISTS ppl_clean AFTER DELETE ON people BEGIN DELETE FROM part_answers WHERE OLD.id=part_answers.id_part;END;")
	end

	# Find a `People` by its id, or `null` if it could not be found
	fun find_people_by_id(id: Int): nullable People do
		var req = select("* from people where id={id};")
		for i in req do
			return new People.from_db(i[0].to_i, i[1].to_s, i[2].to_s)
		end
		return null
	end

	# Find a `Meetup` by its id or `null` if it could not be found
	fun find_meetup_by_id(id: String): nullable Meetup do
		var req = select("* FROM meetups where id={id.to_sql_string};")
		for i in req do
			return new Meetup.from_db(i[0].to_s, i[1].to_s, i[2].to_s, i[3].to_s, i[4].to_i)
		end
		return null
	end

	# Find an `Answer` by its `id` or `null` if it could not be found
	fun find_answer_by_id(id: Int): nullable Answer do
		var req = select("* FROM answers WHERE id={id};")
		for i in req do
			return new Answer.from_db(i[0].to_i, i[2].to_s)
		end
		return null
	end

	# Change an Answer `ansid` for someone with an id `pid` to `resp`
	#
	# Returns `true` if the request was sucessful, false otherwise
	fun change_answer(pid: Int, ansid: Int, resp: Int): Bool do
		var p = find_people_by_id(pid)
		if p == null then
			print "Error while updating answer {ansid}:{pid}"
			return false
		end
		var a = find_answer_by_id(ansid)
		if a == null then
			print "Error while updating answer {ansid}:{pid}"
			return false
		end
		p.answers[a] = resp
		if p.commit(self) then return true
		return false
	end

	# Removes a person in the Database by its `id`
	#
	# Returns true if sucessful, false otherwise
	fun remove_people_by_id(id: Int): Bool do
		var rq = execute("DELETE FROM people WHERE id = {id};")
		if not rq then
			print "Cannot delete people {id}"
			print error or else "Unknown error"
			return false
		end
		return true
	end
end

# Any kind of Database Object that can be persisted to the database
abstract class DBObject

	# Commits the modifications done to the Object in the database
	fun commit(db: OpportunityDB): Bool is abstract
end

# A Meetup participant, linked to the DB
class People
	super DBObject

	# ID in the Database, -1 if not set
	var id: Int = -1
	# Name of the participant
	var name: String
	# Surname of the participant
	var surname: String
	# Map of the answers of a Meetup and the answers of the participant
	# 0 = No
	# 1 = Maybe
	# 2 = Yes
	var answers: Map[Answer, Int] = new HashMap[Answer, Int]

	# To be used internally when fetching the `People` in Database
	private init from_db(id: Int, name, surname: String) do
		init(name, surname)
		self.id = id
	end

	# Changes an answer `ans` (or adds it)
	fun answer=(ans: Answer, resp: Int) do
		answers[ans] = resp
	end

	# Loads the answers for a Meetup
	#
	# NOTE: If `self` does not exist in the Database, no answers will be fetched
	fun load_answers(db: OpportunityDB, meetup: Meetup) do
		self.answers = new HashMap[Answer, Int]
		var req = db.select("answers.id, answers.name, part_answers.value FROM part_answers, answers WHERE part_answers.id_part={id} AND answers.id=part_answers.id_ans AND answers.meetup_id={meetup.id.html_escape.to_sql_string} GROUP BY answers.id;")
		for i in req do
			var ans = new Answer.from_db(i[0].to_i, i[1].to_s)
			answers[ans] = i[2].to_i
		end
	end

	redef fun to_s do return "{surname.capitalized} {name.capitalized}"

	redef fun commit(db) do
		if id == -1 then
			if not db.execute("INSERT INTO people (name,surname) VALUES ({name.html_escape.to_sql_string}, {surname.html_escape.to_sql_string});") then
				print "Error while adding people {self}"<commit_msg>contrib/opportunity: do not force capitalize names<commit_after># This file is part of NIT ( http://www.nitlanguage.org ).
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License

# Model for the data of Opportunity
module opportunity_model

import sqlite3
import sha1
import serialization

# A SQLiteDB object for `Opportunity`
class OpportunityDB
	super Sqlite3DB

	init open(x) do
		super

		create_db
	end

	# Creates the tables and triggers for Opportunity (SQLite3 DB)
	fun create_db do
		assert create_table("IF NOT EXISTS meetups (id CHAR(40) PRIMARY KEY, name TEXT, date TEXT, place TEXT, answer_mode INTEGER DEFAULT 0);") else
			print error or else "?"
		end
		assert create_table("IF NOT EXISTS people(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, surname TEXT);") else
			print error or else "?"
		end
		assert create_table("IF NOT EXISTS answers(id INTEGER PRIMARY KEY AUTOINCREMENT, meetup_id CHAR(40), name TEXT, FOREIGN KEY(meetup_id) REFERENCES meetups(id));") else
			print error or else "?"
		end
		assert create_table("IF NOT EXISTS part_answers(id_part INTEGER, id_ans INTEGER, value INTEGER, FOREIGN KEY(id_part) REFERENCES people(id), FOREIGN KEY(id_ans) REFERENCES answers(id));") else
			print error or else "?"
		end
		#NOTE: The following triggers could be replaced by ON DELETE CASCADE clauses
		# Thing is, SQLite does not seem to support those operations (well, not by default, it seems
		# we must re-compile the lib to support it. So, well, let's just create triggers heh.
		assert execute("CREATE TRIGGER IF NOT EXISTS answers_clean AFTER DELETE ON meetups BEGIN DELETE FROM answers WHERE answers.meetup_id=OLD.id;END;") else
			print error or else "?"
		end
		assert execute("CREATE TRIGGER IF NOT EXISTS ans_clean AFTER DELETE ON answers BEGIN DELETE FROM part_answers WHERE OLD.id=part_answers.id_ans;END;") else
			print error or else "?"
		end
		assert execute("CREATE TRIGGER IF NOT EXISTS ppl_clean AFTER DELETE ON people BEGIN DELETE FROM part_answers WHERE OLD.id=part_answers.id_part;END;")
	end

	# Find a `People` by its id, or `null` if it could not be found
	fun find_people_by_id(id: Int): nullable People do
		var req = select("* from people where id={id};")
		for i in req do
			return new People.from_db(i[0].to_i, i[1].to_s, i[2].to_s)
		end
		return null
	end

	# Find a `Meetup` by its id or `null` if it could not be found
	fun find_meetup_by_id(id: String): nullable Meetup do
		var req = select("* FROM meetups where id={id.to_sql_string};")
		for i in req do
			return new Meetup.from_db(i[0].to_s, i[1].to_s, i[2].to_s, i[3].to_s, i[4].to_i)
		end
		return null
	end

	# Find an `Answer` by its `id` or `null` if it could not be found
	fun find_answer_by_id(id: Int): nullable Answer do
		var req = select("* FROM answers WHERE id={id};")
		for i in req do
			return new Answer.from_db(i[0].to_i, i[2].to_s)
		end
		return null
	end

	# Change an Answer `ansid` for someone with an id `pid` to `resp`
	#
	# Returns `true` if the request was sucessful, false otherwise
	fun change_answer(pid: Int, ansid: Int, resp: Int): Bool do
		var p = find_people_by_id(pid)
		if p == null then
			print "Error while updating answer {ansid}:{pid}"
			return false
		end
		var a = find_answer_by_id(ansid)
		if a == null then
			print "Error while updating answer {ansid}:{pid}"
			return false
		end
		p.answers[a] = resp
		if p.commit(self) then return true
		return false
	end

	# Removes a person in the Database by its `id`
	#
	# Returns true if sucessful, false otherwise
	fun remove_people_by_id(id: Int): Bool do
		var rq = execute("DELETE FROM people WHERE id = {id};")
		if not rq then
			print "Cannot delete people {id}"
			print error or else "Unknown error"
			return false
		end
		return true
	end
end

# Any kind of Database Object that can be persisted to the database
abstract class DBObject

	# Commits the modifications done to the Object in the database
	fun commit(db: OpportunityDB): Bool is abstract
end

# A Meetup participant, linked to the DB
class People
	super DBObject

	# ID in the Database, -1 if not set
	var id: Int = -1
	# Name of the participant
	var name: String
	# Surname of the participant
	var surname: String
	# Map of the answers of a Meetup and the answers of the participant
	# 0 = No
	# 1 = Maybe
	# 2 = Yes
	var answers: Map[Answer, Int] = new HashMap[Answer, Int]

	# To be used internally when fetching the `People` in Database
	private init from_db(id: Int, name, surname: String) do
		init(name, surname)
		self.id = id
	end

	# Changes an answer `ans` (or adds it)
	fun answer=(ans: Answer, resp: Int) do
		answers[ans] = resp
	end

	# Loads the answers for a Meetup
	#
	# NOTE: If `self` does not exist in the Database, no answers will be fetched
	fun load_answers(db: OpportunityDB, meetup: Meetup) do
		self.answers = new HashMap[Answer, Int]
		var req = db.select("answers.id, answers.name, part_answers.value FROM part_answers, answers WHERE part_answers.id_part={id} AND answers.id=part_answers.id_ans AND answers.meetup_id={meetup.id.html_escape.to_sql_string} GROUP BY answers.id;")
		for i in req do
			var ans = new Answer.from_db(i[0].to_i, i[1].to_s)
			answers[ans] = i[2].to_i
		end
	end

	redef fun to_s do return "{surname} {name}"

	redef fun commit(db) do
		if id == -1 then
			if not db.execute("INSERT INTO people (name,surname) VALUES ({name.html_escape.to_sql_string}, {surname.html_escape.to_sql_string});") then
				print "Error while adding people {self}"
{'fraction_non_alphanumeric': 0.08635868198205751, 'fraction_numerical': 0.005365976356166681, 'mean_word_length': 4.064968152866242, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, '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': '244055', 'n_tokens_mistral': 4280, 'n_tokens_neox': 3870, 'n_words': 1600}