Add unit tests. Make DbStore handle cases where the bug type or category
[cfb.git] / prod / net / jaekl / cfb / store / DbStore.java
1 package net.jaekl.cfb.store;
2
3 import java.sql.Connection;
4 import java.sql.SQLException;
5 import java.util.List;
6 import java.util.Locale;
7
8 import net.jaekl.cfb.CfbBundle;
9 import net.jaekl.cfb.analyze.Analysis;
10 import net.jaekl.cfb.db.CfbSchema;
11 import net.jaekl.cfb.db.Column;
12 import net.jaekl.cfb.db.Condition;
13 import net.jaekl.cfb.db.Operation;
14 import net.jaekl.cfb.db.Row;
15 import net.jaekl.cfb.db.Sort;
16 import net.jaekl.cfb.db.Table;
17 import net.jaekl.cfb.db.TypeMismatchException;
18 import net.jaekl.cfb.db.driver.DbDriver;
19 import net.jaekl.cfb.xml.BugCollection;
20 import net.jaekl.cfb.xml.BugInstance;
21 import net.jaekl.cfb.xml.LocalVariable;
22 import net.jaekl.cfb.xml.messages.BugCategory;
23 import net.jaekl.cfb.xml.messages.BugPattern;
24 import net.jaekl.cfb.xml.messages.MessageCollection;
25
26 public class DbStore {
27         Connection m_con;
28         DbDriver m_driver;
29         MessageCollection m_msgColl;
30         
31         public DbStore(Connection con, DbDriver driver, MessageCollection msgColl) {
32                 m_con = con;
33                 m_driver = driver;
34                 m_msgColl = msgColl;
35         }
36
37         public Analysis getPrior(Analysis analysis) throws SQLException, TypeMismatchException {
38                 if (null == analysis) {
39                         return null;
40                 }
41                 Long priorId = getPriorId(analysis);
42                 if (null == priorId) {
43                         return null;
44                 }
45                 
46                 return getAnalysis(priorId);
47         }
48         
49         public boolean put(Analysis analysis) throws SQLException, TypeMismatchException, StoreException {
50                 CfbBundle bundle = CfbBundle.getInst(Locale.getDefault());
51                 
52                 if (null == analysis) {
53                         return false;
54                 }
55                 
56                 // ----------------------------------
57                 // Add a run record for this analysis
58                 
59                 long runId = m_driver.nextVal(m_con, CfbSchema.RUN_SEQ);
60                 Object[][] values = { 
61                                                                 {
62                                                                         Long.valueOf(runId),
63                                                                         analysis.getProjectName(),
64                                                                         analysis.getBuildNumber(),
65                                                                         analysis.getStart(),
66                                                                         analysis.getEnd() 
67                                                                 } 
68                                                         };
69                 int count = m_driver.insert(m_con, CfbSchema.RUNS, values);
70                 if (1 != count) {
71                         return false;
72                 }
73                 
74                 // -------------------------------------
75                 // Add a found record for each bug found
76                 
77                 List<BugInstance> bugs = analysis.getBugCollection().getBugs();
78                 values = new Object[bugs.size()][CfbSchema.FOUND.getNumColumns()];
79                 
80                 int row = 0;
81                 for (BugInstance bug : bugs)
82                 {
83                         Long foundId = Long.valueOf(m_driver.nextVal(m_con, CfbSchema.FOUND_SEQ));
84                         Long bugId = Long.valueOf(m_msgColl.getPattern(bug.getType()).getId());
85                         Long categoryId = Long.valueOf(m_msgColl.getCategory(bug.getCategory()).getId());
86                         List<Location> locs = bug.getLocations();
87                         Location firstLoc  = (locs.size() > 0) ? locs.get(0) : null;
88                         Location secondLoc = (locs.size() > 1) ? locs.get(1) : null;
89                         Location thirdLoc  = (locs.size() > 2) ? locs.get(2) : null;
90                         
91                         if (BugPattern.UNKNOWN.getId() == bugId) {
92                                 throw new StoreException(bundle.get(CfbBundle.BUG_TYPE_UNKNOWN, ""+bug.getType()));
93                         }
94                         if (BugCategory.UNKNOWN.getId() == categoryId) {
95                                 throw new StoreException(bundle.get(CfbBundle.BUG_CATEGORY_UNKNOWN, ""+bug.getCategory()));
96                         }
97                         
98                         values[row][0] = foundId;
99                         values[row][1] = runId;
100                         values[row][2] = bugId;
101                         values[row][3] = categoryId;
102                         values[row][4] = getLocId(firstLoc);
103                         values[row][5] = getLocId(secondLoc);
104                         values[row][6] = getLocId(thirdLoc);
105                         values[row][7] = getVarId(bug);
106                         row++;
107                 }
108                 
109                 count = m_driver.insert(m_con, CfbSchema.FOUND, values);
110                 return (bugs.size() == count);
111         }
112         
113         String getBugType(Long bugPatternId) throws SQLException, TypeMismatchException
114         {
115                 Column[] columns = { CfbSchema.TYPE };
116                 Table[] tables = { CfbSchema.BUGS };
117                 Condition[] conditions = { new Condition(CfbSchema.BUGID, bugPatternId, Operation.EQUAL) }; 
118                 Row row = m_driver.selectExactlyOne(m_con, columns, tables, conditions);
119                 String type = row.getString(0);
120                 return type;
121         }
122         
123         String getCategoryName(Long categoryId) throws SQLException, TypeMismatchException
124         {
125                 Column[] columns = { CfbSchema.CATEGORY };
126                 Table[] tables = { CfbSchema.CATEGORIES };
127                 Condition[] conditions = { new Condition(CfbSchema.CATEGORYID, categoryId, Operation.EQUAL) };
128                 Row row = m_driver.selectExactlyOne(m_con, columns, tables, conditions);
129                 String name = row.getString(0);
130                 return name;
131         }
132         
133         
134         Location getLoc(Long locId) throws SQLException, TypeMismatchException
135         {
136                 if (null == locId) {
137                         return null;
138                 }
139                 
140                 Column[] columns = { CfbSchema.CLASSNAME, CfbSchema.METHODNAME, CfbSchema.METHODROLE, CfbSchema.STARTLINE, CfbSchema.ENDLINE };
141                 Table[] tables = { CfbSchema.LOCATIONS };
142                 Condition[] conditions = { new Condition(CfbSchema.LOCID, locId, Operation.EQUAL) };
143                 
144                 Row row = m_driver.selectExactlyOne(m_con, columns, tables, conditions);
145                 
146                 String className = row.getString(0);
147                 String methodName = row.getString(1);
148                 String methodRole = row.getString(2);
149                 Integer startLine = row.getInt(3);
150                 Integer endLine = row.getInt(4);
151                 
152                 Location loc = new Location(locId, className, methodName, methodRole, startLine, endLine);
153                 return loc;
154         }
155         
156         Long getLocId(Location loc) throws SQLException, TypeMismatchException 
157         {
158                 if (null == loc) {
159                         return null;
160                 }
161                 Long locId = findLocId(loc);
162                 if (null != locId) {
163                         return locId;
164                 }
165
166                 return storeLoc(loc);
167         }
168         
169         Long findLocId(Location loc) throws SQLException, TypeMismatchException
170         {
171                 Column[] columns = { CfbSchema.LOCID };
172                 Table[] tables = { CfbSchema.LOCATIONS };
173                 
174                 Condition[] conditions = { 
175                                                 new Condition( CfbSchema.CLASSNAME,  loc.getClassName(),  Operation.EQUAL ),
176                                                 new Condition( CfbSchema.METHODNAME, loc.getMethodName(), Operation.EQUAL ),
177                                                 new Condition( CfbSchema.METHODROLE, loc.getMethodRole(), Operation.EQUAL ),
178                                                 new Condition( CfbSchema.STARTLINE,  loc.getStart(),      Operation.EQUAL ),
179                                                 new Condition( CfbSchema.ENDLINE,    loc.getEnd(),        Operation.EQUAL )
180                                         };
181                 List<Row> rows = m_driver.select(m_con, columns, tables, conditions);
182                 if (rows.size() > 0) {
183                         assert(1 == rows.size());       // should only have one match
184                         
185                         return rows.get(0).getLong(0);
186                 }
187                 
188                 return null;    // not found
189         }
190         
191         Long storeLoc(Location loc) throws SQLException
192         {
193                 long locId = m_driver.nextVal(m_con, CfbSchema.LOC_SEQ);
194                 
195                 Object[][] values = { { 
196                                                         Long.valueOf(locId),
197                                                         loc.getClassName(),
198                                                         loc.getMethodName(),
199                                                         loc.getMethodRole(),
200                                                         Long.valueOf(loc.getStart()),
201                                                         Long.valueOf(loc.getEnd())
202                                                 } };
203                 int count = m_driver.insert(m_con, CfbSchema.LOCATIONS, values);
204                 if (1 != count) {
205                         return null;
206                 }
207                 
208                 return Long.valueOf(locId);
209         }
210         
211         Long getVarId(BugInstance bug) throws SQLException, TypeMismatchException
212         {
213                 if (null == bug) {
214                         return null;
215                 }
216                 
217                 List<LocalVariable> vars = bug.getVariables();
218                 if ((null == vars) || (0 == vars.size())) {
219                         return null;
220                 }
221                 
222                 return getVarId(vars.get(0));
223         }
224         
225         LocalVariable getVar(Long varId) throws SQLException, TypeMismatchException
226         {
227                 if (null == varId) {
228                         return null;
229                 }
230                 
231                 Column[] columns = { CfbSchema.NAME, CfbSchema.VARROLE };
232                 Table[] tables = { CfbSchema.VARIABLES };
233                 Condition[] conditions = { new Condition(CfbSchema.VARID_PK, varId, Operation.EQUAL) };
234                 
235                 List<Row> result = m_driver.select(m_con, columns, tables, conditions);
236                 if (result.size() < 1) {
237                         throw new SQLException("No variable found for ID " + varId);
238                 }
239                 if (result.size() > 1) {
240                         throw new SQLException("Too many matches (" + result.size() + ") found for variable ID " + varId);
241                 }
242                 
243                 String varName = result.get(0).getString(0);
244                 String varRole = result.get(0).getString(1);
245                 
246                 return new LocalVariable(varId, varName, varRole);
247         }
248         
249         Long getVarId(LocalVariable var) throws SQLException, TypeMismatchException
250         {
251                 if (null == var) {
252                         return null;
253                 }
254                 
255                 Long result = findVarId(var);
256                 
257                 if (null != result) {
258                         return result;
259                 }
260                 
261                 return storeVar(var);
262         }
263         
264         Long findVarId(LocalVariable var) throws SQLException, TypeMismatchException
265         {
266                 Column[] columns = { CfbSchema.VARID_PK };
267                 Table[] tables = { CfbSchema.VARIABLES };
268                 
269                 Condition[] conditions = { 
270                                                 new Condition( CfbSchema.NAME,    var.getName(), Operation.EQUAL ),
271                                                 new Condition( CfbSchema.VARROLE, var.getRole(), Operation.EQUAL )
272                                         };
273                 List<Row> rows = m_driver.select(m_con, columns, tables, conditions);
274                 if (rows.size() > 0) {
275                         assert(1 == rows.size());       // should only have one match
276                         
277                         return rows.get(0).getLong(0);
278                 }
279                 
280                 return null;    // not found
281         }
282         
283         Long storeVar(LocalVariable var) throws SQLException
284         {
285                 long varId = m_driver.nextVal(m_con, CfbSchema.VARIABLE_SEQ);
286                 
287                 Object[][] values = { { 
288                                                         Long.valueOf(varId),
289                                                         var.getName(),
290                                                         var.getRole()
291                                                 } };
292                 int count = m_driver.insert(m_con, CfbSchema.VARIABLES, values);
293                 if (1 != count) {
294                         return null;
295                 }
296                 
297                 return Long.valueOf(varId);
298         }
299         
300         Long getPriorId(Analysis analysis) throws SQLException, TypeMismatchException
301         {
302                 Column[] columns = { CfbSchema.RUNID };
303                 Table[] tables = { CfbSchema.RUNS };
304                 Condition[] conditions = {
305                         new Condition( CfbSchema.PROJNAME, analysis.getProjectName(), Operation.EQUAL ),
306                         new Condition( CfbSchema.STARTTIME, analysis.getStart(), Operation.LESS_THAN ) 
307                 };
308                 Sort[] sorts = { new Sort( CfbSchema.STARTTIME, Sort.Direction.DESCENDING ) };
309                 int limit = 1;
310                 
311                 List<Row> rows = m_driver.select(m_con, columns, tables, conditions, sorts, limit);
312                 if (rows.size() < 1) {
313                         return null;
314                 }
315                 return rows.get(0).getLong(0);
316         }
317         
318         Analysis getAnalysis(Long analysisId) throws SQLException, TypeMismatchException
319         {
320                 Column[] columns = { CfbSchema.PROJNAME, CfbSchema.VERSION, CfbSchema.STARTTIME, CfbSchema.ENDTIME };
321                 Table[] tables = { CfbSchema.RUNS };
322                 Condition[] conditions = { new Condition( CfbSchema.RUNID, analysisId, Operation.EQUAL ) };
323                 
324                 List<Row> rows = m_driver.select(m_con, columns, tables, conditions);
325                 if (rows.size() < 1) {
326                         return null;
327                 }
328                 
329                 Row row = rows.get(0);
330                 
331                 String projName = row.getString(0);
332                 String version = row.getString(1);
333                 java.util.Date start= row.getDate(2);
334                 java.util.Date end = row.getDate(3);
335                 
336                 Analysis prior = new Analysis(projName, version);
337                 prior.setId(analysisId.longValue());
338                 prior.setStart(start);
339                 prior.setEnd(end);
340                 
341                 prior.setBugCollection(getBugCollection(analysisId));
342                 
343                 return prior;
344         }
345         
346         BugCollection getBugCollection(Long runId) throws SQLException, TypeMismatchException 
347         {
348                 Column[] columns = {
349                                 CfbSchema.FOUNDID,
350                                 CfbSchema.BUGID,
351                                 CfbSchema.CATEGORYID,
352                                 CfbSchema.FIRSTLOCID,
353                                 CfbSchema.SECONDLOCID,
354                                 CfbSchema.THIRDLOCID,
355                                 CfbSchema.VARID_FK 
356                 };
357                 Table[] tables = {
358                                 CfbSchema.FOUND
359                 };
360                 Condition[] conditions = {
361                                 new Condition(CfbSchema.RUNID, runId, Operation.EQUAL)
362                 };
363                 
364                 BugCollection coll = new BugCollection();
365                 
366                 List<Row> rows = m_driver.select(m_con, columns, tables, conditions);
367                 
368                 for (Row row : rows) {
369                         // long foundId = row.getLong(0);
370                         Long bugId = row.getLong(1);
371                         Long categoryId = row.getLong(2);
372                         Long firstLocId = row.getLong(3);
373                         Long secondLocId = row.getLong(4);
374                         Long thirdLocId = row.getLong(5);
375                         Long varId = row.getLong(6);
376                         
377                         String bugType = getBugType(bugId);
378                         String category = getCategoryName(categoryId);
379                         Location[] locations = { getLoc(firstLocId), getLoc(secondLocId), getLoc(thirdLocId) };
380                         LocalVariable[] vars = { getVar(varId) };
381
382                         
383                         BugInstance bug = new BugInstance(bugId, category, bugType, locations, vars);
384                         coll.add(bug);
385                 }
386                 
387                 return coll;
388         }
389 }