Skip to content

Commit cbab93c

Browse files
complete/not-complete message
1 parent 8fc0302 commit cbab93c

2 files changed

Lines changed: 101 additions & 37 deletions

File tree

query/src/org/labkey/query/controllers/QueryMcp.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import java.util.TreeMap;
6262

6363
import static org.apache.commons.lang3.StringUtils.isNotBlank;
64+
import static org.labkey.api.util.StringUtilsLabKey.pluralize;
6465

6566
public class QueryMcp implements McpService.McpImpl
6667
{
@@ -216,7 +217,7 @@ String validateCalculatedColumnExpression(
216217
"Execute a LabKey SQL query and return results as tab-separated values (RFC 4180 TSV). " +
217218
"Use this to inspect actual query results while writing or debugging SQL. " +
218219
"Prefer validateSQL when you only need to check syntax without running the query. " +
219-
"Returns at most 100 rows (default); use offset and limit to page through larger result sets. " +
220+
"Returns at most 100 rows; use offset and limit to page through larger result sets. " +
220221
"Response format: a header row of column names, then one data row per newline, fields tab-separated. " +
221222
"Fields containing tabs, newlines, or double-quotes are RFC 4180 quoted. " +
222223
"On SQL error, the error message is returned as plain text rather than throwing. " +
@@ -245,8 +246,13 @@ String executeSQL(
245246
try
246247
{
247248
StringWriter sw = new StringWriter(2000);
248-
execute.execute(sw);
249-
return sw.toString();
249+
SqlController.SqlExecute.ExecuteResult result = execute.execute(sw);
250+
String message = "\n-- " + pluralize(result.rows(), "row", "rows") + " returned";
251+
if (result.complete())
252+
message += ".";
253+
else
254+
message += ", more may be available (use offset and limit to page).";
255+
return sw + message;
250256
}
251257
catch (Exception x)
252258
{

query/src/org/labkey/query/controllers/SqlController.java

Lines changed: 92 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ public SqlExecute(ContainerUser ctx, UserSchema schema, String sql)
264264
this.format = Format.tsv;
265265
}
266266

267-
SqlExecute page(Integer offset, Integer limit)
267+
SqlExecute page(Integer offset, int limit)
268268
{
269269
this.offset = offset;
270270
this.limit = limit;
@@ -335,41 +335,44 @@ void getStringData(Results rs, ArrayList<String> out) throws SQLException
335335
}
336336
}
337337

338-
void writeResults_text(Writer out, Results rs, String sep, String eol) throws IOException, SQLException
338+
int writeResults_text(Writer out, Results rs, int limit, String sep, String eol) throws IOException, SQLException
339339
{
340340
initWriter(rs);
341-
final int count = rs.getMetaData().getColumnCount();
341+
final int columnCount = rs.getMetaData().getColumnCount();
342342

343343
// meta-meta-data
344344
out.write("18.2"+sep+"name"+sep+"jdbcType"+eol);
345345

346-
for (int column = 1; column <= count; column++)
346+
for (int column = 1; column <= columnCount; column++)
347347
{
348348
out.write(rs.getColumn(column).getName());
349-
out.write(column == count ? eol : sep);
349+
out.write(column == columnCount ? eol : sep);
350350
}
351351

352-
for (int column = 1; column <= count; column++)
352+
for (int column = 1; column <= columnCount; column++)
353353
{
354354
int index = column-1;
355355
out.write(types[index].name());
356-
out.write(column == count ? eol : sep);
356+
out.write(column == columnCount ? eol : sep);
357357
}
358358

359-
ArrayList<String> values = new ArrayList<>(count);
359+
ArrayList<String> values = new ArrayList<>(columnCount);
360360

361-
while (rs.next())
361+
int count = 0;
362+
while (--limit >= 0 && rs.next())
362363
{
363364
getStringData(rs, values);
364-
for (int index = 0; index < count; index++)
365+
for (int index = 0; index < columnCount; index++)
365366
{
366367
String s = values.get(index);
367368
if (null != s)
368369
out.write(s);
369-
out.write(index == count - 1 ? eol : sep);
370+
out.write(index == columnCount - 1 ? eol : sep);
370371
}
372+
count++;
371373
}
372374
out.flush();
375+
return count;
373376
}
374377

375378
/**
@@ -387,35 +390,36 @@ void writeResults_text(Writer out, Results rs, String sep, String eol) throws IO
387390
* implementation is generating strings inside out.write(). So this is probably not much different from a GC
388391
* perspective.
389392
*/
390-
void writeResults_compact(Writer out, Results rs, String sep, String eol) throws IOException, SQLException
393+
int writeResults_compact(Writer out, Results rs, int limit, String sep, String eol) throws IOException, SQLException
391394
{
392395
initWriter(rs);
393-
final int count = rs.getMetaData().getColumnCount();
396+
final int columnCount = rs.getMetaData().getColumnCount();
394397

395398
// meta-meta-data
396399
out.write("18.2"+sep+"name"+sep+"jdbcType"+eol);
397400

398-
for (int column = 1; column <= count; column++)
401+
for (int column = 1; column <= columnCount; column++)
399402
{
400403
out.write(rs.getColumn(column).getName());
401-
out.write(column == count ? eol : sep);
404+
out.write(column == columnCount ? eol : sep);
402405
}
403406

404-
for (int index = 0; index < count; index++)
407+
for (int index = 0; index < columnCount; index++)
405408
{
406409
out.write(types[index].name());
407-
out.write(index == count-1 ? eol : sep);
410+
out.write(index == columnCount-1 ? eol : sep);
408411
}
409412

410413
String DITTO = String.valueOf(BS);
411-
ArrayList<String> prev = new ArrayList<>(count);
412-
ArrayList<String> row = new ArrayList<>(count);
414+
ArrayList<String> prev = new ArrayList<>(columnCount);
415+
ArrayList<String> row = new ArrayList<>(columnCount);
413416

414-
while (rs.next())
417+
int count = 0;
418+
while (--limit >= 0 && rs.next())
415419
{
416420
getStringData(rs, row);
417421

418-
for (int index = 0; index < count; index++)
422+
for (int index = 0; index < columnCount; index++)
419423
{
420424
String s = row.get(index);
421425
if (null != s && !s.isEmpty())
@@ -425,63 +429,80 @@ void writeResults_compact(Writer out, Results rs, String sep, String eol) throws
425429
else
426430
out.write(s);
427431
}
428-
out.write(index == count - 1 ? eol : sep);
432+
out.write(index == columnCount - 1 ? eol : sep);
429433
}
430434
ArrayList<String> t = prev;
431435
prev = row;
432436
row = t;
437+
count++;
433438
}
434439
out.flush();
440+
return count;
435441
}
436442

437443
/// export a Result set using RFC4180 formatting
438444
/// use PageFlowUtil.joinValuesWithTabs4180
439-
void writeResults_tsv(Writer out, Results rs) throws IOException, SQLException
445+
int writeResults_tsv(Writer out, Results rs, int limit) throws IOException, SQLException
440446
{
441447
initWriter(rs);
442-
final int count = rs.getMetaData().getColumnCount();
448+
final int columnCount = rs.getMetaData().getColumnCount();
443449

444-
List<String> names = new ArrayList<>(count);
445-
for (int column = 1; column <= count; column++)
450+
List<String> names = new ArrayList<>(columnCount);
451+
for (int column = 1; column <= columnCount; column++)
446452
names.add(rs.getColumn(column).getName());
447453
out.write(PageFlowUtil.joinValuesWithTabs4180(names));
448454
out.write('\n');
449455

450-
ArrayList<String> values = new ArrayList<>(count);
456+
ArrayList<String> values = new ArrayList<>(columnCount);
451457

452-
while (rs.next())
458+
int count = 0;
459+
while (--limit >= 0 && rs.next())
453460
{
454461
getStringData(rs, values);
455462
out.write(PageFlowUtil.joinValuesWithTabs4180(values));
456463
out.write('\n');
464+
count++;
457465
}
458466
out.flush();
467+
return count;
459468
}
460469

470+
public record ExecuteResult(int rows, boolean complete) {}
461471

462-
public void execute(Writer out) throws SQLException, IOException
472+
public ExecuteResult execute(Writer out) throws SQLException, IOException
463473
{
464474
schema.checkCanReadSchema();
465475
var builder = QueryService.get().getSelectBuilder(schema, sql, true);
466476
if (null != offset)
467477
builder.offset(offset);
468-
if (null != limit)
469-
builder.maxRows(limit);
478+
int maxPrint = Integer.MAX_VALUE;
479+
if (null != limit && limit < Integer.MAX_VALUE)
480+
{
481+
builder.maxRows(limit + 1);
482+
maxPrint = limit;
483+
}
470484

471485
try (Results rs = builder.select(false, parameterMap))
472486
{
487+
int count;
488+
boolean complete = true;
473489
switch (format)
474490
{
475491
case tsv:
476-
writeResults_tsv(out, rs);
492+
count = writeResults_tsv(out, rs, maxPrint);
477493
break;
478494
case split:
479-
writeResults_text(out, rs, sep, eol);
495+
count = writeResults_text(out, rs, maxPrint, sep, eol);
480496
break;
481497
case compact:
482-
writeResults_compact(out, rs, sep, eol);
498+
count = writeResults_compact(out, rs, maxPrint, sep, eol);
483499
break;
500+
default:
501+
throw new IllegalArgumentException("Unknown format: " + format);
484502
}
503+
while (rs.next())
504+
complete = false;
505+
return new ExecuteResult(count, complete);
485506
}
486507
}
487508
}
@@ -835,5 +856,42 @@ public void testTruncation() throws Exception
835856
assertEquals("Bob", lines[2]); // 3 == 3: not truncated (boundary)
836857
assertEquals("Car…[truncated]", lines[3]); // 5 > 3: truncated
837858
}
859+
860+
private SqlExecute.ExecuteResult executeWithLimit(int limit) throws Exception
861+
{
862+
User user = TestContext.get().getUser();
863+
UserSchema listsSchema = (UserSchema) DefaultSchema.get(user, _folder).getSchema("lists");
864+
ContainerUser cu = ContainerUser.create(_folder, user);
865+
var execute = new SqlExecute(cu, listsSchema, "SELECT Name FROM " + LIST_NAME + " ORDER BY Name")
866+
.page(0, limit);
867+
return execute.execute(new StringWriter());
868+
}
869+
870+
@Test
871+
public void testLimit_lessThanRowCount() throws Exception
872+
{
873+
// 3 rows in table, limit=2 → incomplete result
874+
var result = executeWithLimit(2);
875+
assertEquals(2, result.rows());
876+
assertFalse("Expected more rows to be available", result.complete());
877+
}
878+
879+
@Test
880+
public void testLimit_equalToRowCount() throws Exception
881+
{
882+
// 3 rows in table, limit=3 → complete result
883+
var result = executeWithLimit(3);
884+
assertEquals(3, result.rows());
885+
assertTrue("Expected result to be complete", result.complete());
886+
}
887+
888+
@Test
889+
public void testLimit_greaterThanRowCount() throws Exception
890+
{
891+
// 3 rows in table, limit=10 → complete result
892+
var result = executeWithLimit(10);
893+
assertEquals(3, result.rows());
894+
assertTrue("Expected result to be complete", result.complete());
895+
}
838896
}
839897
}

0 commit comments

Comments
 (0)