Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions docs/tz-block-d-external-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,86 @@
- **Метод `extension.getTreeSchema`**: Возвращает первичную структуру бокового меню (например, корневые папки "Databases" и "Users").

Разделение логики: Ядро занимается пикселями и дизайном, Плагин — логикой и структурами данных.

---

## 5. Стандарт мутаций данных (Querya Extension Mutation Standard)

Если плагин поддерживает интерактивное редактирование данных в 2D-таблицах (`ExtensionDriverCapabilities.supportsMutations: true`), он реализует следующие JSON-RPC методы:

### 5.1. `db.getTableSchema`
Возвращает метаданные колонок, признак первичного ключа и возможность `NULL`.
* **Запрос:**
```json
{
"jsonrpc": "2.0",
"method": "db.getTableSchema",
"params": {
"connectionId": 123,
"database": "analytics",
"schema": "public",
"tableName": "users"
},
"id": 4
}
```
* **Ответ:**
```json
{
"jsonrpc": "2.0",
"result": {
"tableName": "users",
"schema": "public",
"primaryKeys": ["id"],
"columns": [
{ "name": "id", "dataType": "integer", "isPrimaryKey": true, "isNullable": false },
{ "name": "email", "dataType": "varchar", "isPrimaryKey": false, "isNullable": true },
{ "name": "age", "dataType": "integer", "isPrimaryKey": false, "isNullable": true }
]
},
"id": 4
}
```

### 5.2. `db.mutate`
Выполняет атомарный пакет мутаций (вставка, обновление, удаление строк).
* **Запрос:**
```json
{
"jsonrpc": "2.0",
"method": "db.mutate",
"params": {
"connectionId": 123,
"database": "analytics",
"tableName": "users",
"mutations": [
{
"type": "update",
"where": { "id": "42" },
"set": { "email": "new_email@domain.com" }
},
{
"type": "insert",
"values": { "id": "43", "email": "bob@domain.com", "age": "30" }
},
{
"type": "delete",
"where": { "id": "10" }
}
]
},
"id": 5
}
```
* **Ответ:**
```json
{
"jsonrpc": "2.0",
"result": {
"success": true,
"affectedRows": 3
},
"id": 5
}
```

48 changes: 48 additions & 0 deletions lib/core/extensions/extension_driver_session.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as p;
import 'package:querya_desktop/core/database/table_schema_meta.dart';
import 'package:querya_desktop/core/extensions/extension_driver_catalog.dart';
import 'package:querya_desktop/core/extensions/extension_support.dart';
import 'package:querya_desktop/core/extensions/local_extension_registry.dart';
Expand Down Expand Up @@ -378,6 +379,53 @@ class ExtensionDriverSession {
}
}

/// Queries table schema metadata (column types, nullability, PKs) via `db.getTableSchema`.
Future<TableSchemaMeta> getTableSchema(
ConnectionRow row, {
required String database,
String? schema,
required String tableName,
}) async {
final bridge = await ensureConnected(row);
try {
final result = await bridge.sendRequest('db.getTableSchema', {
'connectionId': row.id,
'database': database,
if (schema != null && schema.isNotEmpty) 'schema': schema,
'tableName': tableName,
});
if (result is Map) {
return TableSchemaMeta.fromJson(Map<String, dynamic>.from(result));
}
return TableSchemaMeta(tableName: tableName, schema: schema);
} catch (e) {
debugPrint('ExtensionDriverSession getTableSchema fallback ($e)');
return TableSchemaMeta(tableName: tableName, schema: schema);
}
}

/// Executes batch data mutations (insert, update, delete) via `db.mutate`.
Future<Map<String, dynamic>> mutate(
ConnectionRow row, {
required String database,
String? schema,
required String tableName,
required List<Map<String, dynamic>> mutations,
}) async {
final bridge = await ensureConnected(row);
final result = await bridge.sendRequest('db.mutate', {
'connectionId': row.id,
'database': database,
if (schema != null && schema.isNotEmpty) 'schema': schema,
'tableName': tableName,
'mutations': mutations,
});
if (result is Map) {
return Map<String, dynamic>.from(result);
}
return {'success': true, 'affectedRows': mutations.length};
}

Future<void> disconnect(int connectionId) async {
final bridge = _bridges.remove(connectionId);
_manifests.remove(connectionId);
Expand Down
21 changes: 20 additions & 1 deletion lib/core/extensions/models/extension_driver_capabilities.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class ExtensionDriverCapabilities {
this.supportsDDLInspection = false,
this.supportsPrivileges = false,
this.hasServerStats = false,
this.supportsMutations = false,
this.supportsBatchMutations = false,
});

/// True if `db.query` supports transaction control queries (BEGIN, COMMIT, ROLLBACK).
Expand All @@ -23,6 +25,12 @@ class ExtensionDriverCapabilities {
/// True if the driver supports `db.getServerStats`.
final bool hasServerStats;

/// True if the driver supports `db.getTableSchema` and `db.mutate`.
final bool supportsMutations;

/// True if the driver supports batch multi-row mutations in `db.mutate`.
final bool supportsBatchMutations;

factory ExtensionDriverCapabilities.fromRpc(Object? raw) {
if (raw is! Map) return const ExtensionDriverCapabilities();
final map = raw is Map<String, dynamic>
Expand All @@ -42,6 +50,11 @@ class ExtensionDriverCapabilities {
map['supports_privileges'] == true,
hasServerStats:
map['hasServerStats'] == true || map['has_server_stats'] == true,
supportsMutations:
map['supportsMutations'] == true || map['supports_mutations'] == true,
supportsBatchMutations:
map['supportsBatchMutations'] == true ||
map['supports_batch_mutations'] == true,
);
}

Expand All @@ -51,6 +64,8 @@ class ExtensionDriverCapabilities {
'supportsDDLInspection': supportsDDLInspection,
'supportsPrivileges': supportsPrivileges,
'hasServerStats': hasServerStats,
'supportsMutations': supportsMutations,
'supportsBatchMutations': supportsBatchMutations,
};

@override
Expand All @@ -62,7 +77,9 @@ class ExtensionDriverCapabilities {
supportsCancel == other.supportsCancel &&
supportsDDLInspection == other.supportsDDLInspection &&
supportsPrivileges == other.supportsPrivileges &&
hasServerStats == other.hasServerStats;
hasServerStats == other.hasServerStats &&
supportsMutations == other.supportsMutations &&
supportsBatchMutations == other.supportsBatchMutations;

@override
int get hashCode =>
Expand All @@ -72,5 +89,7 @@ class ExtensionDriverCapabilities {
supportsDDLInspection,
supportsPrivileges,
hasServerStats,
supportsMutations,
supportsBatchMutations,
);
}
Loading
Loading