@@ -402,6 +402,79 @@ class GenericSqlWorkspaceState extends material.State<GenericSqlWorkspace> {
402402
403403 /// Runs the selection, else the whole text. With [statementAtCursor] (run
404404 /// statement) only the statement under the caret, see [SqlStatementSplitter] .
405+ final Map <String , SqlResultGridSchema > _gridSchemaCache = {};
406+
407+ static String _schemaCacheKey (String sql, List <String > cols) =>
408+ '${cols .join ('\u 0001' )}\u 0000$sql ' ;
409+
410+ static final _readQueryRegex = RegExp (
411+ r'^\s*(select|with|values|show|explain|pragma|describe)\b' ,
412+ caseSensitive: false ,
413+ );
414+
415+ static bool _isReadQuery (String sql) => _readQueryRegex.hasMatch (sql);
416+
417+ String _resultStatus (
418+ SqlExecutionResult result,
419+ List <String > cols,
420+ List <List <String >> outRows,
421+ String ? editHint,
422+ ) {
423+ if (result.statusMessage != null ) {
424+ return withEditHint (result.statusMessage! , editHint);
425+ }
426+ if (cols.isEmpty && outRows.isEmpty) {
427+ return result.affectedRows != null
428+ ? 'OK. Rows affected: ${result .affectedRows }.'
429+ : 'Command completed.' ;
430+ }
431+ final cap = _resultMaxRows;
432+ return withEditHint (
433+ result.isTruncated
434+ ? 'Showing first $cap row(s) (result capped).'
435+ : '${outRows .length } row(s).' ,
436+ editHint,
437+ );
438+ }
439+
440+ /// Applies the table schema to the result it was asked for. An answer that
441+ /// arrives after the tab has moved on to another result is dropped.
442+ void _applyGridSchema (
443+ SqlQueryTabSession session,
444+ String userSql,
445+ List <String > cols,
446+ List <List <String >> outRows,
447+ SqlResultGridSchema gridSchema,
448+ SqlExecutionResult result,
449+ ) {
450+ if (! mounted ||
451+ ! identical (session.rows, outRows) ||
452+ session.lastExecutedSql != userSql) {
453+ return ;
454+ }
455+ final pks = gridSchema.primaryKeys;
456+ final editHint = gridSchema.editHint (cols);
457+ final canSave = sqlResultGridSaveEnabled (
458+ sql: userSql,
459+ resultColumns: cols,
460+ primaryKeys: pks,
461+ );
462+ setState (() {
463+ session.resultGridPrimaryKeys = canSave ? pks : const [];
464+ session.resultGridColumnDataTypes = gridSchema.columnDataTypes;
465+ session.resultGridColumnMeta = gridSchema.columnMeta;
466+ session.stagingBuffer? .dispose ();
467+ session.stagingBuffer = canSave
468+ ? DataGridStagingBuffer (
469+ columns: cols,
470+ rows: outRows,
471+ primaryKeys: pks,
472+ )
473+ : null ;
474+ session.statusLine = _resultStatus (result, cols, outRows, editHint);
475+ });
476+ }
477+
405478 Future <void > execute ([
406479 SqlQueryTabSession ? targetSession,
407480 bool statementAtCursor = false ,
@@ -479,51 +552,42 @@ class GenericSqlWorkspaceState extends material.State<GenericSqlWorkspace> {
479552 final cols = result.columns;
480553 final outRows = result.rows;
481554
482- final gridSchema = await widget.delegate.resolveTableSchema (userSql, cols);
483- final pks = gridSchema.primaryKeys;
484- final editHint = gridSchema.editHint (cols);
485- final canSave = sqlResultGridSaveEnabled (
486- sql: userSql,
487- resultColumns: cols,
488- primaryKeys: pks,
489- );
490-
555+ // Rows show now. The table schema (keys, types, edit hint) follows: from
556+ // the cache, or from the delegate in the background.
557+ final cacheKey = _schemaCacheKey (userSql, cols);
558+ final cached = _gridSchemaCache[cacheKey];
491559 invalidatePane (session);
492560 setState (() {
493561 session.columns = cols;
494562 session.rows = outRows;
495563 session.affectedRows = result.affectedRows;
496564 session.lastExecutedSql = userSql;
497- session.resultGridPrimaryKeys = canSave ? pks : const [];
498- session.resultGridColumnDataTypes = gridSchema.columnDataTypes ;
499- session.resultGridColumnMeta = gridSchema.columnMeta ;
565+ session.resultGridPrimaryKeys = const [];
566+ session.resultGridColumnDataTypes = null ;
567+ session.resultGridColumnMeta = null ;
500568 session.stagingBuffer? .dispose ();
501- session.stagingBuffer = canSave
502- ? DataGridStagingBuffer (
503- columns: cols,
504- rows: outRows,
505- primaryKeys: pks,
506- )
507- : null ;
508-
509- if (result.statusMessage != null ) {
510- session.statusLine = withEditHint (result.statusMessage! , editHint);
511- } else if (cols.isEmpty && outRows.isEmpty) {
512- session.statusLine = result.affectedRows != null
513- ? 'OK. Rows affected: ${result .affectedRows }.'
514- : 'Command completed.' ;
515- } else {
516- final n = outRows.length;
517- final cap = _resultMaxRows;
518- session.statusLine = withEditHint (
519- result.isTruncated
520- ? 'Showing first $cap row(s) (result capped).'
521- : '$n row(s).' ,
522- editHint,
523- );
524- }
569+ session.stagingBuffer = null ;
570+ session.statusLine = _resultStatus (result, cols, outRows, null );
525571 session.running = false ;
526572 });
573+ // Anything that is not a read may change table shapes: forget the cache.
574+ if (! _isReadQuery (userSql)) _gridSchemaCache.clear ();
575+
576+ if (cached != null ) {
577+ _applyGridSchema (session, userSql, cols, outRows, cached, result);
578+ } else {
579+ unawaited (
580+ widget.delegate.resolveTableSchema (userSql, cols).then (
581+ (gridSchema) {
582+ if (! mounted) return ;
583+ _gridSchemaCache[cacheKey] = gridSchema;
584+ _applyGridSchema (
585+ session, userSql, cols, outRows, gridSchema, result);
586+ },
587+ onError: (_) {}, // The rows stay without save.
588+ ),
589+ );
590+ }
527591
528592 sw.stop ();
529593 final duration = result.elapsed ?? sw.elapsed;
0 commit comments