Mon, 05 Aug 2024 19:38:47 +0200
fix removing filter not working
fixes #407
/* * Copyright 2021 Mike Becker. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.uapcore.lightpit.dao import de.uapcore.lightpit.entities.* import de.uapcore.lightpit.types.CommitRef import de.uapcore.lightpit.types.IssueHistoryType import de.uapcore.lightpit.types.RelationType import de.uapcore.lightpit.types.WebColor import de.uapcore.lightpit.viewmodel.ComponentSummary import de.uapcore.lightpit.viewmodel.IssueSummary import de.uapcore.lightpit.viewmodel.VersionSummary import org.intellij.lang.annotations.Language import java.sql.Connection import java.sql.PreparedStatement import java.sql.ResultSet class PostgresDataAccessObject(private val connection: Connection) : DataAccessObject { /** * Prepares the given [sql] statement and executes the [block] function with the prepared statement as receiver. * The statement is then closed properly. */ private fun <R> withStatement(@Language("SQL") sql: String, block: PreparedStatement.() -> R) = connection.prepareStatement(sql).use(block) /** * Prepares the given [sql] statement and executes the [block] function on that statement. * The statement is then closed properly. */ private fun <R> useStatement(@Language("SQL") sql: String, block: (PreparedStatement) -> R) = connection.prepareStatement(sql).use(block) /** * Executes the statement and iterates the whole result set extracting the rows with the given [extractor] function. */ private fun <T> PreparedStatement.queryAll(extractor: (ResultSet) -> T): List<T> = executeQuery().use { sequence { while (it.next()) { yield(extractor(it)) } }.toList() } /** * Executes the statement and extracts a single row with the given [extractor] function. * If the result set is empty, null is returned. */ private fun <T> PreparedStatement.querySingle(extractor: (ResultSet) -> T): T? = executeQuery().use { return if (it.next()) extractor(it) else null } //<editor-fold desc="User"> //language=SQL private val userQuery = "select userid, username, lastname, givenname, mail from lpit_user" private fun ResultSet.extractUser() = User(getInt("userid")).apply { username = getString("username") givenname = getString("givenname") lastname = getString("lastname") mail = getString("mail") } private fun ResultSet.containsUserInfo(): Boolean { getInt("userid") return !wasNull() } private fun ResultSet.extractOptionalUser() = if (containsUserInfo()) extractUser() else null override fun listUsers() = withStatement("$userQuery where userid > 0 order by username") { queryAll { it.extractUser() } } override fun findUser(id: Int): User? = withStatement("$userQuery where userid = ?") { setInt(1, id) querySingle { it.extractUser() } } override fun findUserByName(username: String): User? = withStatement("$userQuery where lower(username) = lower(?)") { setString(1, username) querySingle { it.extractUser() } } override fun insertUser(user: User) { withStatement("insert into lpit_user (username, lastname, givenname, mail) values (?, ?, ?, ?)") { with(user) { setStringSafe(1, username) setStringOrNull(2, lastname) setStringOrNull(3, givenname) setStringOrNull(4, mail) } executeUpdate() } } override fun updateUser(user: User) { withStatement("update lpit_user set lastname = ?, givenname = ?, mail = ? where userid = ?") { with(user) { setStringOrNull(1, lastname) setStringOrNull(2, givenname) setStringOrNull(3, mail) setInt(4, id) } executeUpdate() } } //</editor-fold> //<editor-fold desc="Version"> //language=SQL private val versionQuery = "select versionid, project, name, node, ordinal, status, release, eol from lpit_version" private fun ResultSet.extractVersion() = Version(getInt("versionid"), getInt("project")).apply { name = getString("name") node = getString("node") ordinal = getInt("ordinal") release = getDate("release") eol = getDate("eol") status = getEnum("status") } override fun listVersions(project: Project): List<Version> = withStatement("$versionQuery where project = ? order by ordinal desc, lower(name) desc") { setInt(1, project.id) queryAll { it.extractVersion() } } override fun listVersionSummaries(project: Project): List<VersionSummary> = withStatement( """with version_map as ( select issueid, status, resolved as versionid, true as isresolved from lpit_issue union all select issueid, status, affected as versionid, false as isresolved from lpit_issue ), issues as ( select versionid, phase, isresolved, count(issueid) as total from version_map join lpit_issue_phases using (status) group by versionid, phase, isresolved ), summary as ( select versionid, phase, isresolved, total from lpit_version v left join issues using (versionid) ) select v.versionid, project, name, node, ordinal, status, release, eol, ro.total as resolved_open, ra.total as resolved_active, rd.total as resolved_done, ao.total as affected_open, aa.total as affected_active, ad.total as affected_done from lpit_version v left join summary ro on ro.versionid = v.versionid and ro.phase = 0 and ro.isresolved left join summary ra on ra.versionid = v.versionid and ra.phase = 1 and ra.isresolved left join summary rd on rd.versionid = v.versionid and rd.phase = 2 and rd.isresolved left join summary ao on ao.versionid = v.versionid and ao.phase = 0 and not ao.isresolved left join summary aa on aa.versionid = v.versionid and aa.phase = 1 and not aa.isresolved left join summary ad on ad.versionid = v.versionid and ad.phase = 2 and not ad.isresolved where v.project = ? order by ordinal desc, lower(name) desc """.trimIndent() ) { setInt(1, project.id) queryAll { rs -> VersionSummary(rs.extractVersion()).apply { reportedTotal.open = rs.getInt("affected_open") reportedTotal.active = rs.getInt("affected_active") reportedTotal.done = rs.getInt("affected_done") resolvedTotal.open = rs.getInt("resolved_open") resolvedTotal.active = rs.getInt("resolved_active") resolvedTotal.done = rs.getInt("resolved_done") } } } override fun findVersion(id: Int): Version? = withStatement("$versionQuery where versionid = ?") { setInt(1, id) querySingle { it.extractVersion() } } override fun findVersionByNode(project: Project, node: String): Version? = withStatement("$versionQuery where project = ? and node = ?") { setInt(1, project.id) setString(2, node) querySingle { it.extractVersion() } } override fun insertVersion(version: Version) { withStatement("insert into lpit_version (name, node, ordinal, status, project, release, eol) values (?, ?, ?, ?::version_status, ?, ?, ?)") { with(version) { setStringSafe(1, name) setStringSafe(2, node) setInt(3, ordinal) setEnum(4, status) setInt(5, projectid) setDateOrNull(6, release) setDateOrNull(7, eol) } executeUpdate() } } override fun updateVersion(version: Version) { withStatement("update lpit_version set name = ?, node = ?, ordinal = ?, status = ?::version_status, release=?,eol=? where versionid = ?") { with(version) { setStringSafe(1, name) setStringSafe(2, node) setInt(3, ordinal) setEnum(4, status) setDateOrNull(5, version.release) setDateOrNull(6, version.eol) setInt(7, id) } executeUpdate() } } //</editor-fold> //<editor-fold desc="Component"> //language=SQL private val componentQuery = """ select id, project, name, node, color, ordinal, description, active, userid, username, givenname, lastname, mail from lpit_component left join lpit_user on lead = userid """.trimIndent() private fun ResultSet.extractComponent(): Component = Component(getInt("id"), getInt("project")).apply { name = getString("name") node = getString("node") color = try { WebColor(getString("color")) } catch (ex: IllegalArgumentException) { WebColor("000000") } ordinal = getInt("ordinal") description = getString("description") active = getBoolean("active") lead = extractOptionalUser() } private fun PreparedStatement.setComponent(index: Int, component: Component): Int { with(component) { var i = index setStringSafe(i++, name) setStringSafe(i++, node) setStringSafe(i++, color.hex) setInt(i++, ordinal) setStringOrNull(i++, description) setBoolean(i++, active) setIntOrNull(i++, lead?.id) return i } } override fun listComponents(project: Project): List<Component> = withStatement("$componentQuery where project = ? order by ordinal, lower(name)") { setInt(1, project.id) queryAll { it.extractComponent() } } override fun listComponentSummaries(project: Project): List<ComponentSummary> = withStatement( """ with issues as ( select component, phase, count(issueid) as total from lpit_issue join lpit_issue_phases using (status) group by component, phase ), summary as ( select c.id, phase, total from lpit_component c left join issues i on c.id = i.component ) select c.id, project, name, node, color, ordinal, description, active, userid, username, givenname, lastname, mail, open.total as open, wip.total as wip, done.total as done from lpit_component c left join lpit_user on lead = userid left join summary open on c.id = open.id and open.phase = 0 left join summary wip on c.id = wip.id and wip.phase = 1 left join summary done on c.id = done.id and done.phase = 2 where c.project = ? order by ordinal, name """.trimIndent() ) { setInt(1, project.id) queryAll { rs -> ComponentSummary(rs.extractComponent()).apply { issueSummary.open = rs.getInt("open") issueSummary.active = rs.getInt("wip") issueSummary.done = rs.getInt("done") } } } override fun findComponent(id: Int): Component? = withStatement("$componentQuery where id = ?") { setInt(1, id) querySingle { it.extractComponent() } } override fun findComponentByNode(project: Project, node: String): Component? = withStatement("$componentQuery where project = ? and node = ?") { setInt(1, project.id) setString(2, node) querySingle { it.extractComponent() } } override fun insertComponent(component: Component) { withStatement("insert into lpit_component (name, node, color, ordinal, description, active, lead, project) values (?, ?, ?, ?, ?, ?, ?, ?)") { val col = setComponent(1, component) setInt(col, component.projectid) executeUpdate() } } override fun updateComponent(component: Component) { withStatement("update lpit_component set name = ?, node = ?, color = ?, ordinal = ?, description = ?, active = ?, lead = ? where id = ?") { val col = setComponent(1, component) setInt(col, component.id) executeUpdate() } } //</editor-fold> //<editor-fold desc="Project"> //language=SQL private val projectQuery = """ select projectid, name, node, ordinal, description, vcs, repourl, userid, username, lastname, givenname, mail from lpit_project left join lpit_user owner on lpit_project.owner = owner.userid """.trimIndent() private fun ResultSet.extractProject() = Project(getInt("projectid")).apply { name = getString("name") node = getString("node") ordinal = getInt("ordinal") description = getString("description") vcs = getEnum("vcs") repoUrl = getString("repourl") owner = extractOptionalUser() } private fun PreparedStatement.setProject(index: Int, project: Project): Int { var i = index with(project) { setStringSafe(i++, name) setStringSafe(i++, node) setInt(i++, ordinal) setStringOrNull(i++, description) setEnum(i++, vcs) setStringOrNull(i++, repoUrl) setIntOrNull(i++, owner?.id) } return i } override fun listProjects(): List<Project> = withStatement("$projectQuery order by ordinal, lower(name)") { queryAll { it.extractProject() } } override fun findProject(id: Int): Project? = withStatement("$projectQuery where projectid = ?") { setInt(1, id) querySingle { it.extractProject() } } override fun findProjectByNode(node: String): Project? = withStatement("$projectQuery where node = ?") { setString(1, node) querySingle { it.extractProject() } } override fun insertProject(project: Project) { withStatement("insert into lpit_project (name, node, ordinal, description, vcs, repourl, owner) values (?, ?, ?, ?, ?::vcstype, ?, ?)") { setProject(1, project) executeUpdate() } } override fun updateProject(project: Project) { withStatement("update lpit_project set name = ?, node = ?, ordinal = ?, description = ?, vcs = ?::vcstype, repourl = ?, owner = ? where projectid = ?") { val col = setProject(1, project) setInt(col, project.id) executeUpdate() } } override fun collectIssueSummary(project: Project): IssueSummary = withStatement( """ select phase, count(*) as total from lpit_issue join lpit_issue_phases using(status) where project = ? group by phase """.trimIndent() ) { setInt(1, project.id) executeQuery().use { val summary = IssueSummary() while (it.next()) { val phase = it.getInt("phase") val total = it.getInt("total") when (phase) { 0 -> summary.open = total 1 -> summary.active = total 2 -> summary.done = total } } summary } } override fun collectIssueSummary(assignee: User): IssueSummary = withStatement( """ select phase, count(*) as total from lpit_issue join lpit_issue_phases using(status) where assignee = ? group by phase """.trimIndent() ) { setInt(1, assignee.id) executeQuery().use { val summary = IssueSummary() while (it.next()) { val phase = it.getInt("phase") val total = it.getInt("total") when (phase) { 0 -> summary.open = total 1 -> summary.active = total 2 -> summary.done = total } } summary } } override fun mergeCommitRefs(refs: List<CommitRef>) { withStatement("insert into lpit_commit_ref (issueid, commit_hash, commit_brief) values (?,?,?) on conflict do nothing") { refs.forEach { ref -> setInt(1, ref.issueId) setString(2, ref.hash) setString(3, ref.message) executeUpdate() } } } //</editor-fold> //<editor-fold desc="Issue"> //language=SQL private val issueQuery = """ select issueid, i.project, p.name as projectname, p.node as projectnode, component, c.name as componentname, c.node as componentnode, status, phase, category, subject, i.description, userid, username, givenname, lastname, mail, created, updated, eta, affected, resolved from lpit_issue i join lpit_project p on i.project = projectid join lpit_issue_phases using (status) left join lpit_component c on component = c.id left join lpit_user on userid = assignee """.trimIndent() private fun ResultSet.extractIssue(): Issue { val proj = Project(getInt("project")).apply { name = getString("projectname") node = getString("projectnode") } val comp = getInt("component").let { if (wasNull()) null else Component(it, proj.id).apply { name = getString("componentname") node = getString("componentnode") } } val issue = Issue(getInt("issueid"), proj).apply { component = comp status = getEnum("status") category = getEnum("category") subject = getString("subject") description = getString("description") assignee = extractOptionalUser() created = getTimestamp("created") updated = getTimestamp("updated") eta = getDate("eta") affected = getInt("affected").takeIf { it > 0 }?.let { findVersion(it) } resolved = getInt("resolved").takeIf { it > 0 }?.let { findVersion(it) } } return issue } private fun PreparedStatement.setIssue(index: Int, issue: Issue): Int { var i = index with(issue) { setIntOrNull(i++, component?.id) setEnum(i++, status) setEnum(i++, category) setStringSafe(i++, subject) setStringOrNull(i++, description) setIntOrNull(i++, assignee?.id) setDateOrNull(i++, eta) setIntOrNull(i++, affected?.id) setIntOrNull(i++, resolved?.id) } return i } override fun listIssues(includeDone: Boolean): List<Issue> = withStatement("$issueQuery where (? or phase < 2)") { setBoolean(1, includeDone) queryAll { it.extractIssue() } } override fun listIssues(project: Project, includeDone: Boolean): List<Issue> = withStatement("$issueQuery where i.project = ? and (? or phase < 2)") { setInt(1, project.id) setBoolean(2, includeDone) queryAll { it.extractIssue() } } override fun listIssues( project: Project, includeDone: Boolean, specificVersion: Boolean, version: Version?, specificComponent: Boolean, component: Component? ): List<Issue> = withStatement( """$issueQuery where i.project = ? and (? or phase < 2) and (not ? or ? in (resolved, affected)) and (not ? or (resolved is null and affected is null)) and (not ? or component = ?) and (not ? or component is null) """.trimIndent() ) { setInt(1, project.id) setBoolean(2, includeDone) setBoolean(3, specificVersion && version != null) setInt(4, version?.id ?: 0) setBoolean(5, specificVersion && version == null) setBoolean(6, specificComponent && component != null) setInt(7, component?.id ?: 0) setBoolean(8, specificComponent && component == null) queryAll { it.extractIssue() } } override fun findIssue(id: Int): Issue? = withStatement("$issueQuery where issueid = ?") { setInt(1, id) querySingle { it.extractIssue() } } override fun insertIssue(issue: Issue): Int { val id = withStatement( """ insert into lpit_issue (component, status, category, subject, description, assignee, eta, affected, resolved, project) values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?, ?, ?) returning issueid """.trimIndent() ) { val col = setIssue(1, issue) setInt(col, issue.project.id) querySingle { it.getInt(1) }!! } return id } override fun updateIssue(issue: Issue) { withStatement( """ update lpit_issue set updated = now(), component = ?, status = ?::issue_status, category = ?::issue_category, subject = ?, description = ?, assignee = ?, eta = ?, affected = ?, resolved = ? where issueid = ? """.trimIndent() ) { val col = setIssue(1, issue) setInt(col, issue.id) executeUpdate() } } override fun insertHistoryEvent(issue: Issue, newId: Int) { val type = if (newId > 0) IssueHistoryType.New else IssueHistoryType.Update val issueid = if (newId > 0) newId else issue.id val eventid = withStatement("insert into lpit_issue_history_event(issueid, subject, type) values (?,?,?::issue_history_event) returning eventid") { setInt(1, issueid) setString(2, issue.subject) setEnum(3, type) querySingle { it.getInt(1) }!! } withStatement( """ insert into lpit_issue_history_data (component, status, category, description, assignee, eta, affected, resolved, eventid) values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?, ?) """.trimIndent() ) { setStringOrNull(1, issue.component?.name) setEnum(2, issue.status) setEnum(3, issue.category) setStringOrNull(4, issue.description) setStringOrNull(5, issue.assignee?.shortDisplayname) setDateOrNull(6, issue.eta) setStringOrNull(7, issue.affected?.name) setStringOrNull(8, issue.resolved?.name) setInt(9, eventid) executeUpdate() } } override fun listCommitRefs(issue: Issue): List<CommitRef> = withStatement("select commit_hash, commit_brief from lpit_commit_ref where issueid = ?") { setInt(1, issue.id) queryAll { CommitRef( issueId = issue.id, hash = it.getString("commit_hash"), message = it.getString("commit_brief") ) } } //</editor-fold> //<editor-fold desc="Issue Relations"> override fun insertIssueRelation(rel: IssueRelation) { withStatement( """ insert into lpit_issue_relation (from_issue, to_issue, type) values (?, ?, ?::relation_type) on conflict do nothing """.trimIndent() ) { if (rel.reverse) { setInt(2, rel.from.id) setInt(1, rel.to.id) } else { setInt(1, rel.from.id) setInt(2, rel.to.id) } setEnum(3, rel.type) executeUpdate() } } override fun deleteIssueRelation(rel: IssueRelation) { withStatement("delete from lpit_issue_relation where from_issue = ? and to_issue = ? and type=?::relation_type") { if (rel.reverse) { setInt(2, rel.from.id) setInt(1, rel.to.id) } else { setInt(1, rel.from.id) setInt(2, rel.to.id) } setEnum(3, rel.type) executeUpdate() } } override fun listIssueRelations(issue: Issue): List<IssueRelation> = buildList { withStatement("select to_issue, type from lpit_issue_relation where from_issue = ?") { setInt(1, issue.id) queryAll { IssueRelation(issue, findIssue(it.getInt("to_issue"))!!, it.getEnum("type"), false) } }.forEach(this::add) withStatement("select from_issue, type from lpit_issue_relation where to_issue = ?") { setInt(1, issue.id) queryAll { IssueRelation(issue, findIssue(it.getInt("from_issue"))!!, it.getEnum("type"), true) } }.forEach(this::add) } override fun getIssueRelationMap(project: Project, includeDone: Boolean): IssueRelationMap = getIssueRelationMapImpl(project, includeDone) override fun getIssueRelationMap(includeDone: Boolean): IssueRelationMap = getIssueRelationMapImpl(null, includeDone) private fun getIssueRelationMapImpl(project: Project?, includeDone: Boolean): IssueRelationMap = withStatement( """ select r.from_issue, r.to_issue, r.type from lpit_issue_relation r join lpit_issue i on i.issueid = r.from_issue join lpit_issue_phases p on i.status = p.status where (? or i.project = ?) and (? or p.phase < 2) """.trimIndent() ) { setBoolean(1, project == null) setInt(2, project?.id ?: 0) setBoolean(3, includeDone) queryAll { Pair(it.getInt("from_issue"), Pair(it.getInt("to_issue"), it.getEnum<RelationType>("type"))) } }.groupBy({it.first},{it.second}) //</editor-fold> //<editor-fold desc="IssueComment"> private fun ResultSet.extractIssueComment() = IssueComment(getInt("commentid"), getInt("issueid")).apply { created = getTimestamp("created") updated = getTimestamp("updated") updateCount = getInt("updatecount") comment = getString("comment") author = extractOptionalUser() } override fun listComments(issue: Issue): List<IssueComment> = withStatement("select * from lpit_issue_comment left join lpit_user using (userid) where issueid = ? order by created") { setInt(1, issue.id) queryAll { it.extractIssueComment() } } override fun findComment(id: Int): IssueComment? = withStatement("select * from lpit_issue_comment left join lpit_user using (userid) where commentid = ?") { setInt(1, id) querySingle { it.extractIssueComment() } } override fun insertComment(issueComment: IssueComment): Int = useStatement("update lpit_issue set updated = now() where issueid = ?") { updateIssueDate -> withStatement("insert into lpit_issue_comment (issueid, comment, userid) values (?, ? ,?) returning commentid") { with(issueComment) { updateIssueDate.setInt(1, issueid) setInt(1, issueid) setStringSafe(2, comment) setIntOrNull(3, author?.id) } val commentid = querySingle { it.getInt(1) }!! updateIssueDate.executeUpdate() commentid } } override fun updateComment(issueComment: IssueComment) { useStatement("update lpit_issue set updated = now() where issueid = ?") { updateIssueDate -> withStatement("update lpit_issue_comment set comment = ?, updatecount = updatecount + 1, updated = now() where commentid = ?") { with(issueComment) { updateIssueDate.setInt(1, issueid) setStringSafe(1, comment) setInt(2, id) } executeUpdate() updateIssueDate.executeUpdate() } } } override fun insertHistoryEvent(issue: Issue, issueComment: IssueComment, newId: Int) { val type = if (newId > 0) IssueHistoryType.NewComment else IssueHistoryType.UpdateComment val commentid = if (newId > 0) newId else issueComment.id val eventid = withStatement("insert into lpit_issue_history_event(issueid, subject, type) values (?,?,?::issue_history_event) returning eventid") { setInt(1, issueComment.issueid) setString(2, issue.subject) setEnum(3, type) querySingle { it.getInt(1) }!! } withStatement("insert into lpit_issue_comment_history (commentid, eventid, comment) values (?,?,?)") { setInt(1, commentid) setInt(2, eventid) setString(3, issueComment.comment) executeUpdate() } } //</editor-fold> //<editor-fold desc="Issue History"> override fun listIssueHistory(project: Project?, days: Int) = withStatement( """ select p.name as project_name, u.username as current_assignee, evt.*, evtdata.* from lpit_issue_history_event evt join lpit_issue issue using (issueid) join lpit_project p on project = p.projectid left join lpit_user u on u.userid = issue.assignee join lpit_issue_history_data evtdata using (eventid) where (? or project = ?) and time > now() - (? * interval '1' day) order by time desc """.trimIndent() ) { setBoolean(1, project == null) setInt(2, project?.id ?: -1) setInt(3, days) queryAll { rs-> with(rs) { IssueHistoryEntry( project = getString("project_name"), subject = getString("subject"), time = getTimestamp("time"), type = getEnum("type"), currentAssignee = getString("current_assignee"), issueid = getInt("issueid"), component = getString("component") ?: "", status = getEnum("status"), category = getEnum("category"), description = getString("description") ?: "", assignee = getString("assignee") ?: "", eta = getDate("eta"), affected = getString("affected") ?: "", resolved = getString("resolved") ?: "" ) } } } override fun listIssueCommentHistory(project: Project?, days: Int) = withStatement( """ select u.username as current_assignee, evt.*, evtdata.* from lpit_issue_history_event evt join lpit_issue issue using (issueid) left join lpit_user u on u.userid = issue.assignee join lpit_issue_comment_history evtdata using (eventid) where (? or project = ?) and time > now() - (? * interval '1' day) order by time desc """.trimIndent() ) { setBoolean(1, project == null) setInt(2, project?.id ?: -1) setInt(3, days) queryAll { rs-> with(rs) { IssueCommentHistoryEntry( subject = getString("subject"), time = getTimestamp("time"), type = getEnum("type"), currentAssignee = getString("current_assignee"), issueid = getInt("issueid"), commentid = getInt("commentid"), comment = getString("comment") ) } } } //</editor-fold> }