Sat, 04 Jun 2022 18:02:25 +0200
simplify listIssues() interface
1 /*
2 * Copyright 2021 Mike Becker. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 *
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
26 package de.uapcore.lightpit.dao
28 import de.uapcore.lightpit.entities.*
29 import de.uapcore.lightpit.types.IssueHistoryType
30 import de.uapcore.lightpit.types.WebColor
31 import de.uapcore.lightpit.viewmodel.ComponentSummary
32 import de.uapcore.lightpit.viewmodel.IssueSummary
33 import de.uapcore.lightpit.viewmodel.VersionSummary
34 import org.intellij.lang.annotations.Language
35 import java.sql.Connection
36 import java.sql.PreparedStatement
37 import java.sql.ResultSet
39 class PostgresDataAccessObject(private val connection: Connection) : DataAccessObject {
41 /**
42 * Prepares the given [sql] statement and executes the [block] function with the prepared statement as receiver.
43 * The statement is then closed properly.
44 */
45 private fun <R> withStatement(@Language("SQL") sql: String, block: PreparedStatement.() -> R) =
46 connection.prepareStatement(sql).use(block)
48 /**
49 * Prepares the given [sql] statement and executes the [block] function on that statement.
50 * The statement is then closed properly.
51 */
52 private fun <R> useStatement(@Language("SQL") sql: String, block: (PreparedStatement) -> R) =
53 connection.prepareStatement(sql).use(block)
55 /**
56 * Executes the statement and iterates the whole result set extracting the rows with the given [extractor] function.
57 */
58 private fun <T> PreparedStatement.queryAll(extractor: (ResultSet) -> T): List<T> = executeQuery().use {
59 sequence {
60 while (it.next()) {
61 yield(extractor(it))
62 }
63 }.toList()
64 }
66 /**
67 * Executes the statement and extracts a single row with the given [extractor] function.
68 * If the result set is empty, null is returned.
69 */
70 private fun <T> PreparedStatement.querySingle(extractor: (ResultSet) -> T): T? = executeQuery().use {
71 return if (it.next()) extractor(it) else null
72 }
74 //<editor-fold desc="User">
75 //language=SQL
76 private val userQuery = "select userid, username, lastname, givenname, mail from lpit_user"
78 private fun ResultSet.extractUser() = User(getInt("userid")).apply {
79 username = getString("username")
80 givenname = getString("givenname")
81 lastname = getString("lastname")
82 mail = getString("mail")
83 }
85 private fun ResultSet.containsUserInfo(): Boolean {
86 getInt("userid")
87 return !wasNull()
88 }
90 private fun ResultSet.extractOptionalUser() = if (containsUserInfo()) extractUser() else null
92 override fun listUsers() =
93 withStatement("$userQuery where userid > 0 order by username") {
94 queryAll { it.extractUser() }
95 }
97 override fun findUser(id: Int): User? =
98 withStatement("$userQuery where userid = ?") {
99 setInt(1, id)
100 querySingle { it.extractUser() }
101 }
103 override fun findUserByName(username: String): User? =
104 withStatement("$userQuery where lower(username) = lower(?)") {
105 setString(1, username)
106 querySingle { it.extractUser() }
107 }
109 override fun insertUser(user: User) {
110 withStatement("insert into lpit_user (username, lastname, givenname, mail) values (?, ?, ?, ?)") {
111 with(user) {
112 setStringSafe(1, username)
113 setStringOrNull(2, lastname)
114 setStringOrNull(3, givenname)
115 setStringOrNull(4, mail)
116 }
117 executeUpdate()
118 }
119 }
121 override fun updateUser(user: User) {
122 withStatement("update lpit_user set lastname = ?, givenname = ?, mail = ? where userid = ?") {
123 with(user) {
124 setStringOrNull(1, lastname)
125 setStringOrNull(2, givenname)
126 setStringOrNull(3, mail)
127 setInt(4, id)
128 }
129 executeUpdate()
130 }
131 }
132 //</editor-fold>
134 //<editor-fold desc="Version">
135 //language=SQL
136 private val versionQuery = "select versionid, project, name, node, ordinal, status, release, eol from lpit_version"
138 private fun ResultSet.extractVersion() =
139 Version(getInt("versionid"), getInt("project")).apply {
140 name = getString("name")
141 node = getString("node")
142 ordinal = getInt("ordinal")
143 release = getDate("release")
144 eol = getDate("eol")
145 status = getEnum("status")
146 }
148 override fun listVersions(project: Project): List<Version> =
149 withStatement("$versionQuery where project = ? order by ordinal desc, lower(name) desc") {
150 setInt(1, project.id)
151 queryAll { it.extractVersion() }
152 }
154 override fun listVersionSummaries(project: Project): List<VersionSummary> =
155 withStatement(
156 """with
157 version_map as (
158 select issueid, status, resolved as versionid, true as isresolved from lpit_issue
159 union all
160 select issueid, status, affected as versionid, false as isresolved from lpit_issue
161 ), issues as (
162 select versionid, phase, isresolved, count(issueid) as total from version_map
163 join lpit_issue_phases using (status)
164 group by versionid, phase, isresolved
165 ),
166 summary as (
167 select versionid, phase, isresolved, total
168 from lpit_version v
169 left join issues using (versionid)
170 )
171 select v.versionid, project, name, node, ordinal, status, release, eol,
172 ro.total as resolved_open, ra.total as resolved_active, rd.total as resolved_done,
173 ao.total as affected_open, aa.total as affected_active, ad.total as affected_done
174 from lpit_version v
175 left join summary ro on ro.versionid = v.versionid and ro.phase = 0 and ro.isresolved
176 left join summary ra on ra.versionid = v.versionid and ra.phase = 1 and ra.isresolved
177 left join summary rd on rd.versionid = v.versionid and rd.phase = 2 and rd.isresolved
178 left join summary ao on ao.versionid = v.versionid and ao.phase = 0 and not ao.isresolved
179 left join summary aa on aa.versionid = v.versionid and aa.phase = 1 and not aa.isresolved
180 left join summary ad on ad.versionid = v.versionid and ad.phase = 2 and not ad.isresolved
181 where v.project = ?
182 order by ordinal desc, lower(name) desc
183 """.trimIndent()
184 ) {
185 setInt(1, project.id)
186 queryAll { rs ->
187 VersionSummary(rs.extractVersion()).apply {
188 reportedTotal.open = rs.getInt("affected_open")
189 reportedTotal.active = rs.getInt("affected_active")
190 reportedTotal.done = rs.getInt("affected_done")
191 resolvedTotal.open = rs.getInt("resolved_open")
192 resolvedTotal.active = rs.getInt("resolved_active")
193 resolvedTotal.done = rs.getInt("resolved_done")
194 }
195 }
196 }
198 override fun findVersion(id: Int): Version? =
199 withStatement("$versionQuery where versionid = ?") {
200 setInt(1, id)
201 querySingle { it.extractVersion() }
202 }
204 override fun findVersionByNode(project: Project, node: String): Version? =
205 withStatement("$versionQuery where project = ? and node = ?") {
206 setInt(1, project.id)
207 setString(2, node)
208 querySingle { it.extractVersion() }
209 }
211 override fun insertVersion(version: Version) {
212 withStatement("insert into lpit_version (name, node, ordinal, status, project, release, eol) values (?, ?, ?, ?::version_status, ?, ?, ?)") {
213 with(version) {
214 setStringSafe(1, name)
215 setStringSafe(2, node)
216 setInt(3, ordinal)
217 setEnum(4, status)
218 setInt(5, version.projectid)
219 setDateOrNull(6, version.release)
220 setDateOrNull(7, version.eol)
221 }
222 executeUpdate()
223 }
225 }
227 override fun updateVersion(version: Version) {
228 withStatement("update lpit_version set name = ?, node = ?, ordinal = ?, status = ?::version_status, release=?,eol=? where versionid = ?") {
229 with(version) {
230 setStringSafe(1, name)
231 setStringSafe(2, node)
232 setInt(3, ordinal)
233 setEnum(4, status)
234 setDateOrNull(5, version.release)
235 setDateOrNull(6, version.eol)
236 setInt(7, id)
237 }
238 executeUpdate()
239 }
240 }
241 //</editor-fold>
243 //<editor-fold desc="Component">
244 //language=SQL
245 private val componentQuery =
246 """
247 select id, project, name, node, color, ordinal, description, active,
248 userid, username, givenname, lastname, mail
249 from lpit_component
250 left join lpit_user on lead = userid
251 """.trimIndent()
253 private fun ResultSet.extractComponent(): Component =
254 Component(getInt("id"), getInt("project")).apply {
255 name = getString("name")
256 node = getString("node")
257 color = try {
258 WebColor(getString("color"))
259 } catch (ex: IllegalArgumentException) {
260 WebColor("000000")
261 }
262 ordinal = getInt("ordinal")
263 description = getString("description")
264 active = getBoolean("active")
265 lead = extractOptionalUser()
266 }
268 private fun PreparedStatement.setComponent(index: Int, component: Component): Int {
269 with(component) {
270 var i = index
271 setStringSafe(i++, name)
272 setStringSafe(i++, node)
273 setStringSafe(i++, color.hex)
274 setInt(i++, ordinal)
275 setStringOrNull(i++, description)
276 setBoolean(i++, active)
277 setIntOrNull(i++, lead?.id)
278 return i
279 }
280 }
282 override fun listComponents(project: Project): List<Component> =
283 withStatement("$componentQuery where project = ? order by ordinal, lower(name)") {
284 setInt(1, project.id)
285 queryAll { it.extractComponent() }
286 }
288 override fun listComponentSummaries(project: Project): List<ComponentSummary> =
289 withStatement(
290 """
291 with issues as (
292 select component, phase, count(issueid) as total
293 from lpit_issue
294 join lpit_issue_phases using (status)
295 group by component, phase
296 ),
297 summary as (
298 select c.id, phase, total
299 from lpit_component c
300 left join issues i on c.id = i.component
301 )
302 select c.id, project, name, node, color, ordinal, description, active,
303 userid, username, givenname, lastname, mail,
304 open.total as open, wip.total as wip, done.total as done
305 from lpit_component c
306 left join lpit_user on lead = userid
307 left join summary open on c.id = open.id and open.phase = 0
308 left join summary wip on c.id = wip.id and wip.phase = 1
309 left join summary done on c.id = done.id and done.phase = 2
310 where c.project = ?
311 order by ordinal, name
312 """.trimIndent()
313 ) {
314 setInt(1, project.id)
315 queryAll { rs ->
316 ComponentSummary(rs.extractComponent()).apply {
317 issueSummary.open = rs.getInt("open")
318 issueSummary.active = rs.getInt("wip")
319 issueSummary.done = rs.getInt("done")
320 }
321 }
322 }
324 override fun findComponent(id: Int): Component? =
325 withStatement("$componentQuery where id = ?") {
326 setInt(1, id)
327 querySingle { it.extractComponent() }
328 }
330 override fun findComponentByNode(project: Project, node: String): Component? =
331 withStatement("$componentQuery where project = ? and node = ?") {
332 setInt(1, project.id)
333 setString(2, node)
334 querySingle { it.extractComponent() }
335 }
337 override fun insertComponent(component: Component) {
338 withStatement("insert into lpit_component (name, node, color, ordinal, description, active, lead, project) values (?, ?, ?, ?, ?, ?, ?, ?)") {
339 val col = setComponent(1, component)
340 setInt(col, component.projectid)
341 executeUpdate()
342 }
343 }
345 override fun updateComponent(component: Component) {
346 withStatement("update lpit_component set name = ?, node = ?, color = ?, ordinal = ?, description = ?, active = ?, lead = ? where id = ?") {
347 val col = setComponent(1, component)
348 setInt(col, component.id)
349 executeUpdate()
350 }
351 }
353 //</editor-fold>
355 //<editor-fold desc="Project">
357 //language=SQL
358 private val projectQuery =
359 """
360 select projectid, name, node, ordinal, description, repourl,
361 userid, username, lastname, givenname, mail
362 from lpit_project
363 left join lpit_user owner on lpit_project.owner = owner.userid
364 """.trimIndent()
366 private fun ResultSet.extractProject() =
367 Project(getInt("projectid")).apply {
368 name = getString("name")
369 node = getString("node")
370 ordinal = getInt("ordinal")
371 description = getString("description")
372 repoUrl = getString("repourl")
373 owner = extractOptionalUser()
374 }
376 private fun PreparedStatement.setProject(index: Int, project: Project): Int {
377 var i = index
378 with(project) {
379 setStringSafe(i++, name)
380 setStringSafe(i++, node)
381 setInt(i++, ordinal)
382 setStringOrNull(i++, description)
383 setStringOrNull(i++, repoUrl)
384 setIntOrNull(i++, owner?.id)
385 }
386 return i
387 }
389 override fun listProjects(): List<Project> =
390 withStatement("$projectQuery order by ordinal, lower(name)") {
391 queryAll { it.extractProject() }
392 }
394 override fun findProject(id: Int): Project? =
395 withStatement("$projectQuery where projectid = ?") {
396 setInt(1, id)
397 querySingle { it.extractProject() }
398 }
400 override fun findProjectByNode(node: String): Project? =
401 withStatement("$projectQuery where node = ?") {
402 setString(1, node)
403 querySingle { it.extractProject() }
404 }
406 override fun insertProject(project: Project) {
407 withStatement("insert into lpit_project (name, node, ordinal, description, repourl, owner) values (?, ?, ?, ?, ?, ?)") {
408 setProject(1, project)
409 executeUpdate()
410 }
411 }
413 override fun updateProject(project: Project) {
414 withStatement("update lpit_project set name = ?, node = ?, ordinal = ?, description = ?, repourl = ?, owner = ? where projectid = ?") {
415 val col = setProject(1, project)
416 setInt(col, project.id)
417 executeUpdate()
418 }
419 }
421 override fun collectIssueSummary(project: Project): IssueSummary =
422 withStatement(
423 """
424 select phase, count(*) as total
425 from lpit_issue
426 join lpit_issue_phases using(status)
427 where project = ?
428 group by phase
429 """.trimIndent()
430 ) {
431 setInt(1, project.id)
432 executeQuery().use {
433 val summary = IssueSummary()
434 while (it.next()) {
435 val phase = it.getInt("phase")
436 val total = it.getInt("total")
437 when (phase) {
438 0 -> summary.open = total
439 1 -> summary.active = total
440 2 -> summary.done = total
441 }
442 }
443 summary
444 }
445 }
447 //</editor-fold>
449 //<editor-fold desc="Issue">
451 //language=SQL
452 private val issueQuery =
453 """
454 select issueid,
455 i.project, p.name as projectname, p.node as projectnode,
456 component, c.name as componentname, c.node as componentnode,
457 status, category, subject, i.description,
458 userid, username, givenname, lastname, mail,
459 created, updated, eta, affected, resolved
460 from lpit_issue i
461 join lpit_project p on i.project = projectid
462 left join lpit_component c on component = c.id
463 left join lpit_user on userid = assignee
464 """.trimIndent()
466 private fun ResultSet.extractIssue(): Issue {
467 val proj = Project(getInt("project")).apply {
468 name = getString("projectname")
469 node = getString("projectnode")
470 }
471 val comp = getInt("component").let {
472 if (wasNull()) null else
473 Component(it, proj.id).apply {
474 name = getString("componentname")
475 node = getString("componentnode")
476 }
477 }
478 val issue = Issue(getInt("issueid"), proj).apply {
479 component = comp
480 status = getEnum("status")
481 category = getEnum("category")
482 subject = getString("subject")
483 description = getString("description")
484 assignee = extractOptionalUser()
485 created = getTimestamp("created")
486 updated = getTimestamp("updated")
487 eta = getDate("eta")
488 affected = getInt("affected").takeIf { it > 0 }?.let { findVersion(it) }
489 resolved = getInt("resolved").takeIf { it > 0 }?.let { findVersion(it) }
490 }
492 return issue
493 }
495 private fun PreparedStatement.setIssue(index: Int, issue: Issue): Int {
496 var i = index
497 with(issue) {
498 setIntOrNull(i++, component?.id)
499 setEnum(i++, status)
500 setEnum(i++, category)
501 setStringSafe(i++, subject)
502 setStringOrNull(i++, description)
503 setIntOrNull(i++, assignee?.id)
504 setDateOrNull(i++, eta)
505 setIntOrNull(i++, affected?.id)
506 setIntOrNull(i++, resolved?.id)
507 }
508 return i
509 }
511 override fun listIssues(project: Project, version: Version?, component: Component?): List<Issue> =
512 withStatement(
513 """$issueQuery where
514 (not ? or i.project = ?) and
515 (not ? or ? in (resolved, affected)) and (not ? or (resolved is null and affected is null)) and
516 (not ? or component = ?) and (not ? or component is null)
517 """.trimIndent()
518 ) {
519 fun <T : Entity> applyFilter(search: T?, fflag: Int, nflag: Int, idcol: Int) {
520 if (search == null) {
521 setBoolean(fflag, false)
522 setBoolean(nflag, false)
523 setInt(idcol, 0)
524 } else {
525 setBoolean(fflag, true)
526 setBoolean(nflag, false)
527 setInt(idcol, search.id)
528 }
529 }
530 setBoolean(1, true)
531 setInt(2, project.id)
532 applyFilter(version, 3, 5, 4)
533 applyFilter(component, 6, 8, 7)
535 queryAll { it.extractIssue() }
536 }
538 override fun findIssue(id: Int): Issue? =
539 withStatement("$issueQuery where issueid = ?") {
540 setInt(1, id)
541 querySingle { it.extractIssue() }
542 }
544 override fun insertIssue(issue: Issue): Int {
545 val id = withStatement(
546 """
547 insert into lpit_issue (component, status, category, subject, description, assignee, eta, affected, resolved, project)
548 values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?, ?, ?)
549 returning issueid
550 """.trimIndent()
551 ) {
552 val col = setIssue(1, issue)
553 setInt(col, issue.project.id)
554 querySingle { it.getInt(1) }!!
555 }
556 return id
557 }
559 override fun updateIssue(issue: Issue) {
560 withStatement(
561 """
562 update lpit_issue set updated = now(),
563 component = ?, status = ?::issue_status, category = ?::issue_category, subject = ?,
564 description = ?, assignee = ?, eta = ?, affected = ?, resolved = ?
565 where issueid = ?
566 """.trimIndent()
567 ) {
568 val col = setIssue(1, issue)
569 setInt(col, issue.id)
570 executeUpdate()
571 }
572 }
574 override fun insertHistoryEvent(issue: Issue, newId: Int) {
575 val type = if (newId > 0) IssueHistoryType.New else IssueHistoryType.Update
576 val issueid = if (newId > 0) newId else issue.id
578 val eventid =
579 withStatement("insert into lpit_issue_history_event(issueid, subject, type) values (?,?,?::issue_history_event) returning eventid") {
580 setInt(1, issueid)
581 setString(2, issue.subject)
582 setEnum(3, type)
583 querySingle { it.getInt(1) }!!
584 }
585 withStatement(
586 """
587 insert into lpit_issue_history_data (component, status, category, description, assignee, eta, affected, resolved, eventid)
588 values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?, ?)
589 """.trimIndent()
590 ) {
591 setStringOrNull(1, issue.component?.name)
592 setEnum(2, issue.status)
593 setEnum(3, issue.category)
594 setStringOrNull(4, issue.description)
595 setStringOrNull(5, issue.assignee?.shortDisplayname)
596 setDateOrNull(6, issue.eta)
597 setStringOrNull(7, issue.affected?.name)
598 setStringOrNull(8, issue.resolved?.name)
599 setInt(9, eventid)
600 executeUpdate()
601 }
602 }
604 //</editor-fold>
606 //<editor-fold desc="IssueComment">
608 private fun ResultSet.extractIssueComment() =
609 IssueComment(getInt("commentid"), getInt("issueid")).apply {
610 created = getTimestamp("created")
611 updated = getTimestamp("updated")
612 updateCount = getInt("updatecount")
613 comment = getString("comment")
614 author = extractOptionalUser()
615 }
617 override fun listComments(issue: Issue): List<IssueComment> =
618 withStatement("select * from lpit_issue_comment left join lpit_user using (userid) where issueid = ? order by created") {
619 setInt(1, issue.id)
620 queryAll { it.extractIssueComment() }
621 }
623 override fun findComment(id: Int): IssueComment? =
624 withStatement("select * from lpit_issue_comment left join lpit_user using (userid) where commentid = ?") {
625 setInt(1, id)
626 querySingle { it.extractIssueComment() }
627 }
629 override fun insertComment(issueComment: IssueComment): Int =
630 useStatement("update lpit_issue set updated = now() where issueid = ?") { updateIssueDate ->
631 withStatement("insert into lpit_issue_comment (issueid, comment, userid) values (?, ? ,?) returning commentid") {
632 with(issueComment) {
633 updateIssueDate.setInt(1, issueid)
634 setInt(1, issueid)
635 setStringSafe(2, comment)
636 setIntOrNull(3, author?.id)
637 }
638 val commentid = querySingle { it.getInt(1) }!!
639 updateIssueDate.executeUpdate()
640 commentid
641 }
642 }
644 override fun updateComment(issueComment: IssueComment) {
645 useStatement("update lpit_issue set updated = now() where issueid = ?") { updateIssueDate ->
646 withStatement("update lpit_issue_comment set comment = ?, updatecount = updatecount + 1, updated = now() where commentid = ?") {
647 with(issueComment) {
648 updateIssueDate.setInt(1, issueid)
649 setStringSafe(1, comment)
650 setInt(2, id)
651 }
652 executeUpdate()
653 updateIssueDate.executeUpdate()
654 }
655 }
656 }
659 override fun insertHistoryEvent(issue: Issue, issueComment: IssueComment, newId: Int) {
660 val type = if (newId > 0) IssueHistoryType.NewComment else IssueHistoryType.UpdateComment
661 val commentid = if (newId > 0) newId else issueComment.id
663 val eventid =
664 withStatement("insert into lpit_issue_history_event(issueid, subject, type) values (?,?,?::issue_history_event) returning eventid") {
665 setInt(1, issueComment.issueid)
666 setString(2, issue.subject)
667 setEnum(3, type)
668 querySingle { it.getInt(1) }!!
669 }
670 withStatement("insert into lpit_issue_comment_history (commentid, eventid, comment) values (?,?,?)") {
671 setInt(1, commentid)
672 setInt(2, eventid)
673 setString(3, issueComment.comment)
674 executeUpdate()
675 }
676 }
678 //</editor-fold>
680 //<editor-fold desc="Issue History">
682 override fun listIssueHistory(projectId: Int, days: Int) =
683 withStatement(
684 """
685 select u.username as current_assignee, evt.*, evtdata.*
686 from lpit_issue_history_event evt
687 join lpit_issue issue using (issueid)
688 left join lpit_user u on u.userid = issue.assignee
689 join lpit_issue_history_data evtdata using (eventid)
690 where project = ?
691 and time > now() - (? * interval '1' day)
692 order by time desc
693 """.trimIndent()
694 ) {
695 setInt(1, projectId)
696 setInt(2, days)
697 queryAll { rs->
698 with(rs) {
699 IssueHistoryEntry(
700 subject = getString("subject"),
701 time = getTimestamp("time"),
702 type = getEnum("type"),
703 currentAssignee = getString("current_assignee"),
704 issueid = getInt("issueid"),
705 component = getString("component") ?: "",
706 status = getEnum("status"),
707 category = getEnum("category"),
708 description = getString("description") ?: "",
709 assignee = getString("assignee") ?: "",
710 eta = getDate("eta"),
711 affected = getString("affected") ?: "",
712 resolved = getString("resolved") ?: ""
713 )
714 }
715 }
716 }
718 override fun listIssueCommentHistory(projectId: Int, days: Int) =
719 withStatement(
720 """
721 select u.username as current_assignee, evt.*, evtdata.*
722 from lpit_issue_history_event evt
723 join lpit_issue issue using (issueid)
724 left join lpit_user u on u.userid = issue.assignee
725 join lpit_issue_comment_history evtdata using (eventid)
726 where project = ?
727 and time > now() - (? * interval '1' day)
728 order by time desc
729 """.trimIndent()
730 ) {
731 setInt(1, projectId)
732 setInt(2, days)
733 queryAll { rs->
734 with(rs) {
735 IssueCommentHistoryEntry(
736 subject = getString("subject"),
737 time = getTimestamp("time"),
738 type = getEnum("type"),
739 currentAssignee = getString("current_assignee"),
740 issueid = getInt("issueid"),
741 commentid = getInt("commentid"),
742 comment = getString("comment")
743 )
744 }
745 }
746 }
748 //</editor-fold>
749 }