From 6eab5b7530d52be081c0d16ab437da9f6ce92d3d Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 10 Aug 2026 12:41:42 -0400 Subject: [PATCH 1/3] Implement SQL Repos --- .../api/match/db/repos/MatchCycleSqlRepo.java | 147 +++++++++++++ .../api/match/db/repos/MatchSqlRepo.java | 198 ++++++++++++++++++ 2 files changed, 345 insertions(+) create mode 100644 src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java create mode 100644 src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchSqlRepo.java diff --git a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java new file mode 100644 index 0000000..60b6161 --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java @@ -0,0 +1,147 @@ +package org.patinanetwork.patchats.api.match.db.repos; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.patinanetwork.patchats.api.match.db.models.MatchCycle; +import org.springframework.jdbc.core.SqlParameterValue; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.simple.JdbcClient; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class MatchCycleSqlRepo implements MatchCycleRepo { + private final JdbcClient jdbc; + + private MatchCycle parseResultSetToMatchCycle(final ResultSet rs) throws SQLException { + java.sql.Array sqlArray = rs.getArray("unmatched_id"); + List unmatchedIds = sqlArray == null ? List.of() : Arrays.asList((UUID[]) sqlArray.getArray()); + + return MatchCycle.builder() + .id(rs.getInt("id")) + .period(rs.getString("period")) + .runAt(rs.getObject("run_at", Instant.class)) + .totalMembers(rs.getInt("total_members")) + .totalMatched(rs.getInt("total_matched")) + .unmatchedIds(unmatchedIds) + .build(); + } + + private String uuidListToPostgresArrayLiteral(List ids) { + if (ids == null || ids.isEmpty()) { + return null; + } + return "{" + ids.stream().map(UUID::toString).collect(Collectors.joining(",")) + "}"; + } + + @Override + public MatchCycle createMatchCycle(MatchCycle matchCycle) { + String sql = """ + INSERT INTO "match_cycles" ( + "period", + "run_at", + "total_members", + "total_matched", + "unmatched_id" + ) + VALUES( + :period, + :run_at, + :total_members, + :total_matched, + :unmatched_id + ) + RETURNING + * + """; + + String unmatchedLiteral = uuidListToPostgresArrayLiteral(matchCycle.getUnmatchedIds()); + + return jdbc.sql(sql) + .param("period", matchCycle.getPeriod()) + .param("run_at", matchCycle.getRunAt()) + .param("total_members", matchCycle.getTotalMembers()) + .param("total_matched", matchCycle.getTotalMatched()) + .param("unmatched_id", new SqlParameterValue(Types.OTHER, unmatchedLiteral)) + .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) + .single(); + } + + @Override + public Optional updateMatchCycle(MatchCycle matchCycle) { + String sql = """ + UPDATE "match_cycles" SET + "period" = :period, + "run_at" = :run_at, + "total_members" = :total_members, + "total_matched" = :total_matched, + "unmatched_id" = :unmatched_id + WHERE "id" = :id + RETURNING * + """; + + String unmatchedLiteral = uuidListToPostgresArrayLiteral(matchCycle.getUnmatchedIds()); + + return jdbc.sql(sql) + .param("id", matchCycle.getId()) + .param("period", matchCycle.getPeriod()) + .param("run_at", matchCycle.getRunAt()) + .param("total_members", matchCycle.getTotalMembers()) + .param("total_matched", matchCycle.getTotalMatched()) + .param("unmatched_id", new SqlParameterValue(Types.OTHER, unmatchedLiteral)) + .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) + .optional(); + } + + @Override + public Optional getMatchCycleById(Integer id) { + String sql = "SELECT * FROM \"match_cycles\" WHERE \"id\" = :id"; + return jdbc.sql(sql) + .param("id", id) + .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) + .optional(); + } + + @Override + public Optional deleteMatchCycleById(Integer id) { + String sql = "DELETE FROM \"match_cycles\" WHERE \"id\" = :id RETURNING *"; + return jdbc.sql(sql) + .param("id", id) + .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) + .optional(); + } + + @Override + public List filterMatchCycles(MatchCycleFilterCriteria criteria) { + StringBuilder sql = new StringBuilder("SELECT * FROM \"match_cycles\" WHERE 1=1"); + MapSqlParameterSource params = new MapSqlParameterSource(); + + criteria.period().ifPresent(period -> { + sql.append(" AND \"period\" = :period"); + params.addValue("period", period); + }); + + criteria.startTime().ifPresent(start -> { + sql.append(" AND \"run_at\" >= :start_time"); + params.addValue("start_time", start); + }); + + criteria.endTime().ifPresent(end -> { + sql.append(" AND \"run_at\" <= :end_time"); + params.addValue("end_time", end); + }); + + return jdbc.sql(sql.toString()) + .paramSource(params) + .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) + .list(); + } +} diff --git a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchSqlRepo.java b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchSqlRepo.java new file mode 100644 index 0000000..6522074 --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchSqlRepo.java @@ -0,0 +1,198 @@ +package org.patinanetwork.patchats.api.match.db.repos; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.patinanetwork.patchats.api.match.db.models.Match; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.simple.JdbcClient; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class MatchSqlRepo implements MatchRepo { + private final JdbcClient jdbc; + + private Match parseResultSetToMatch(final ResultSet rs) throws SQLException { + return Match.builder() + .id(UUID.fromString(rs.getString("id"))) + .memberAId(UUID.fromString(rs.getString("member_a_id"))) + .memberBId(UUID.fromString(rs.getString("member_b_id"))) + .matchCycleId(rs.getInt("cycle_id")) + .matchScore(rs.getObject("match_score", Double.class)) + .status(rs.getString("status")) + .feedbackA(rs.getString("feedback_a")) + .feedbackB(rs.getString("feedback_b")) + .createdAt(rs.getObject("created_at", Instant.class)) + .build(); + } + + @Override + public Match createMatch(Match match) { + String sql = """ + INSERT INTO "matches" ( + "id", + "member_a_id", + "member_b_id", + "cycle_id", + "match_score", + "status", + "feedback_a", + "feedback_b" + ) + VALUES( + :id, + :member_a_id, + :member_b_id, + :cycle_id, + :match_score, + :status, + :feedback_a, + :feedback_b + ) + RETURNING + * + """; + return jdbc.sql(sql) + .param("id", match.getId()) + .param("member_a_id", match.getMemberAId()) + .param("member_b_id", match.getMemberBId()) + .param("cycle_id", match.getMatchCycleId()) + .param("match_score", match.getMatchScore()) + .param("status", match.getStatus()) + .param("feedback_a", match.getFeedbackA()) + .param("feedback_b", match.getFeedbackB()) + .query((rs, rowNum) -> parseResultSetToMatch(rs)) + .single(); + } + + @Override + public Optional updateMatch(Match match) { + String sql = """ + UPDATE "matches" SET + "member_a_id" = :member_a_id, + "member_b_id" = :member_b_id, + "cycle_id" = :cycle_id, + "match_score" = :match_score, + "status" = :status, + "feedback_a" = :feedback_a, + "feedback_b" = :feedback_b + WHERE "id" = :id + RETURNING * + """; + return jdbc.sql(sql) + .param("id", match.getId()) + .param("member_a_id", match.getMemberAId()) + .param("member_b_id", match.getMemberBId()) + .param("cycle_id", match.getMatchCycleId()) + .param("match_score", match.getMatchScore()) + .param("status", match.getStatus()) + .param("feedback_a", match.getFeedbackA()) + .param("feedback_b", match.getFeedbackB()) + .query((rs, rowNum) -> parseResultSetToMatch(rs)) + .optional(); + } + + @Override + public Optional getMatchById(UUID id) { + String sql = "SELECT * FROM matches WHERE id = :id"; + return jdbc.sql(sql) + .param("id", id) + .query((rs, rowNum) -> parseResultSetToMatch(rs)) + .optional(); + } + + @Override + public Optional setMatchStatus(UUID id, String status) { + String sql = """ + UPDATE "matches" SET "status" = :status + WHERE "id" = :id + RETURNING * + """; + return jdbc.sql(sql) + .param("id", id) + .param("status", status) + .query((rs, rowNum) -> parseResultSetToMatch(rs)) + .optional(); + } + + @Override + public Optional deleteMatchById(UUID id) { + String sql = "DELETE FROM matches WHERE id = :id RETURNING *"; + return jdbc.sql(sql) + .param("id", id) + .query((rs, rowNum) -> parseResultSetToMatch(rs)) + .optional(); + } + + @Override + public Optional recordFeedback(UUID id, UUID memberId, String feedback) { + String sql = """ + UPDATE "matches" SET + "feedback_a" = CASE WHEN "member_a_id" = :member_id THEN :feedback ELSE "feedback_a" END, + "feedback_b" = CASE WHEN "member_b_id" = :member_id THEN :feedback ELSE "feedback_b" END + WHERE "id" = :id AND ("member_a_id" = :member_id OR "member_b_id" = :member_id) + RETURNING * + """; + return jdbc.sql(sql) + .param("id", id) + .param("member_id", memberId) + .param("feedback", feedback) + .query((rs, rowNum) -> parseResultSetToMatch(rs)) + .optional(); + } + + @Override + public List filterMatches(MatchFilterCriteria criteria) { + StringBuilder sql = new StringBuilder("SELECT * FROM matches WHERE 1=1"); + MapSqlParameterSource params = new MapSqlParameterSource(); + + criteria.status().ifPresent(status -> { + sql.append(" AND status = :status"); + params.addValue("status", status); + }); + + criteria.memberId().ifPresent(memberId -> { + sql.append(" AND (member_a_id = :member_id OR member_b_id = :member_id)"); + params.addValue("member_id", memberId); + }); + + criteria.matchCycleId().ifPresent(cycleId -> { + sql.append(" AND cycle_id = :cycle_id"); + params.addValue("cycle_id", cycleId); + }); + + criteria.startTime().ifPresent(start -> { + sql.append(" AND created_at >= :start_time"); + params.addValue("start_time", start); + }); + + criteria.endTime().ifPresent(end -> { + sql.append(" AND created_at <= :end_time"); + params.addValue("end_time", end); + }); + + criteria.period().ifPresent(period -> { + sql.append(" AND cycle_id IN (SELECT id FROM match_cycles WHERE period = :period)"); + params.addValue("period", period); + }); + + criteria.memberIndustry().ifPresent(memberIndustry -> { + sql.append(" AND ("); + sql.append("member_a_id IN (SELECT id FROM members WHERE industry_pref = :member_industry)"); + sql.append(" OR "); + sql.append("member_b_id IN (SELECT id FROM members WHERE industry_pref = :member_industry)"); + sql.append(")"); + params.addValue("member_industry", memberIndustry); + }); + + return jdbc.sql(sql.toString()) + .paramSource(params) + .query((rs, rowNum) -> parseResultSetToMatch(rs)) + .list(); + } +} From caf256c5875dbe094203edcf7aa2f26fb2f944c7 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 14 Aug 2026 15:35:08 -0400 Subject: [PATCH 2/3] Remove unmatched_ids from MatchCycleSqlRepo --- .../api/match/db/repos/MatchCycleSqlRepo.java | 31 ++----------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java index 60b6161..29ec6b0 100644 --- a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java @@ -2,16 +2,11 @@ import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Types; import java.time.Instant; -import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.patinanetwork.patchats.api.match.db.models.MatchCycle; -import org.springframework.jdbc.core.SqlParameterValue; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.simple.JdbcClient; import org.springframework.stereotype.Repository; @@ -22,26 +17,15 @@ public class MatchCycleSqlRepo implements MatchCycleRepo { private final JdbcClient jdbc; private MatchCycle parseResultSetToMatchCycle(final ResultSet rs) throws SQLException { - java.sql.Array sqlArray = rs.getArray("unmatched_id"); - List unmatchedIds = sqlArray == null ? List.of() : Arrays.asList((UUID[]) sqlArray.getArray()); - return MatchCycle.builder() .id(rs.getInt("id")) .period(rs.getString("period")) .runAt(rs.getObject("run_at", Instant.class)) .totalMembers(rs.getInt("total_members")) .totalMatched(rs.getInt("total_matched")) - .unmatchedIds(unmatchedIds) .build(); } - private String uuidListToPostgresArrayLiteral(List ids) { - if (ids == null || ids.isEmpty()) { - return null; - } - return "{" + ids.stream().map(UUID::toString).collect(Collectors.joining(",")) + "}"; - } - @Override public MatchCycle createMatchCycle(MatchCycle matchCycle) { String sql = """ @@ -49,28 +33,23 @@ public MatchCycle createMatchCycle(MatchCycle matchCycle) { "period", "run_at", "total_members", - "total_matched", - "unmatched_id" + "total_matched" ) VALUES( :period, :run_at, :total_members, - :total_matched, - :unmatched_id + :total_matched ) RETURNING * """; - String unmatchedLiteral = uuidListToPostgresArrayLiteral(matchCycle.getUnmatchedIds()); - return jdbc.sql(sql) .param("period", matchCycle.getPeriod()) .param("run_at", matchCycle.getRunAt()) .param("total_members", matchCycle.getTotalMembers()) .param("total_matched", matchCycle.getTotalMatched()) - .param("unmatched_id", new SqlParameterValue(Types.OTHER, unmatchedLiteral)) .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) .single(); } @@ -82,21 +61,17 @@ public Optional updateMatchCycle(MatchCycle matchCycle) { "period" = :period, "run_at" = :run_at, "total_members" = :total_members, - "total_matched" = :total_matched, - "unmatched_id" = :unmatched_id + "total_matched" = :total_matched WHERE "id" = :id RETURNING * """; - String unmatchedLiteral = uuidListToPostgresArrayLiteral(matchCycle.getUnmatchedIds()); - return jdbc.sql(sql) .param("id", matchCycle.getId()) .param("period", matchCycle.getPeriod()) .param("run_at", matchCycle.getRunAt()) .param("total_members", matchCycle.getTotalMembers()) .param("total_matched", matchCycle.getTotalMatched()) - .param("unmatched_id", new SqlParameterValue(Types.OTHER, unmatchedLiteral)) .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) .optional(); } From b63a2c83fe8f375be5660e9abce113c5af4d777a Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 14 Aug 2026 15:42:28 -0400 Subject: [PATCH 3/3] Drop unnecessary quotes from MatchCycleSqlRepo queries --- .../api/match/db/repos/MatchCycleSqlRepo.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java index 29ec6b0..2d9ed6a 100644 --- a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java @@ -78,7 +78,7 @@ public Optional updateMatchCycle(MatchCycle matchCycle) { @Override public Optional getMatchCycleById(Integer id) { - String sql = "SELECT * FROM \"match_cycles\" WHERE \"id\" = :id"; + String sql = "SELECT * FROM match_cycles WHERE id = :id"; return jdbc.sql(sql) .param("id", id) .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) @@ -87,7 +87,7 @@ public Optional getMatchCycleById(Integer id) { @Override public Optional deleteMatchCycleById(Integer id) { - String sql = "DELETE FROM \"match_cycles\" WHERE \"id\" = :id RETURNING *"; + String sql = "DELETE FROM match_cycles WHERE id = :id RETURNING *"; return jdbc.sql(sql) .param("id", id) .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) @@ -96,21 +96,21 @@ public Optional deleteMatchCycleById(Integer id) { @Override public List filterMatchCycles(MatchCycleFilterCriteria criteria) { - StringBuilder sql = new StringBuilder("SELECT * FROM \"match_cycles\" WHERE 1=1"); + StringBuilder sql = new StringBuilder("SELECT * FROM match_cycles WHERE 1=1"); MapSqlParameterSource params = new MapSqlParameterSource(); criteria.period().ifPresent(period -> { - sql.append(" AND \"period\" = :period"); + sql.append(" AND period = :period"); params.addValue("period", period); }); criteria.startTime().ifPresent(start -> { - sql.append(" AND \"run_at\" >= :start_time"); + sql.append(" AND run_at >= :start_time"); params.addValue("start_time", start); }); criteria.endTime().ifPresent(end -> { - sql.append(" AND \"run_at\" <= :end_time"); + sql.append(" AND run_at <= :end_time"); params.addValue("end_time", end); });