Skip to content
Closed
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
6 changes: 5 additions & 1 deletion lib/core/database/sqlite_connection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ class SqliteConnection {
if (!isConnected || _db == null) {
throw StateError('Not connected to SQLite');
}
final sqlLower = sql.trim().toLowerCase();
final sqlLower = sql
.replaceAll(RegExp(r'--.*$', multiLine: true), '')
.replaceAll(RegExp(r'/\*.*?\*/', dotAll: true), '')
.trim()
.toLowerCase();

// SQLite can execute PRAGMA, SELECT, EXPLAIN statements, which return data
final isQuery = sqlLower.startsWith('select') ||
Expand Down
53 changes: 53 additions & 0 deletions lib/core/extensions/models/extension_manifest.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import 'extension_type.dart';

class ExtensionManifest {
final String id;
final String name;
final String version;
final String publisher;
final ExtensionType type;
final Map<String, String> engines;
final String? main;
final String? icon;
final String? description;

const ExtensionManifest({
required this.id,
required this.name,
required this.version,
required this.publisher,
required this.type,
required this.engines,
this.main,
this.icon,
this.description,
});

factory ExtensionManifest.fromJson(Map<String, dynamic> json) {
return ExtensionManifest(
id: json['id'] as String,
name: json['name'] as String,
version: json['version'] as String,
publisher: json['publisher'] as String,
type: ExtensionType.fromString(json['type'] as String),
engines: Map<String, String>.from(json['engines'] as Map? ?? {}),
main: json['main'] as String?,
icon: json['icon'] as String?,
description: json['description'] as String?,
);
}

Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'version': version,
'publisher': publisher,
'type': type.value,
'engines': engines,
if (main != null) 'main': main,
if (icon != null) 'icon': icon,
if (description != null) 'description': description,
};
}
}
15 changes: 15 additions & 0 deletions lib/core/extensions/models/extension_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
enum ExtensionType {
databaseDriver('database_driver'),
theme('theme'),
unknown('unknown');

final String value;
const ExtensionType(this.value);

static ExtensionType fromString(String value) {
return ExtensionType.values.firstWhere(
(e) => e.value == value,
orElse: () => ExtensionType.unknown,
);
}
}
8 changes: 7 additions & 1 deletion lib/features/mysql/mysql_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,13 @@ class _MysqlSqlWorkspaceState extends material.State<MysqlSqlWorkspace> {
}

Future<void> _execute() async {
final userSql = _sqlController.text.trim();
final selection = _sqlController.selection;
String userSql;
if (selection.isValid && !selection.isCollapsed) {
userSql = selection.textInside(_sqlController.text).trim();
} else {
userSql = _sqlController.text.trim();
}
if (userSql.isEmpty) return;

setState(() {
Expand Down
8 changes: 7 additions & 1 deletion lib/features/postgresql/postgres_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,13 @@ class _PostgresSqlWorkspaceState extends material.State<PostgresSqlWorkspace> {
}

Future<void> _execute() async {
final userSql = _sqlController.text.trim();
final selection = _sqlController.selection;
String userSql;
if (selection.isValid && !selection.isCollapsed) {
userSql = selection.textInside(_sqlController.text).trim();
} else {
userSql = _sqlController.text.trim();
}
if (userSql.isEmpty) return;
var sql = injectSqlLimit(userSql, _resultMaxRows);

Expand Down
8 changes: 7 additions & 1 deletion lib/features/sqlite/sqlite_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@ class _SqliteSqlWorkspaceState extends material.State<SqliteSqlWorkspace> {
}

Future<void> _execute() async {
final userSql = _sqlController.text.trim();
final selection = _sqlController.selection;
String userSql;
if (selection.isValid && !selection.isCollapsed) {
userSql = selection.textInside(_sqlController.text).trim();
} else {
userSql = _sqlController.text.trim();
}
if (userSql.isEmpty) return;

setState(() {
Expand Down
79 changes: 79 additions & 0 deletions test/core/extensions/models/extension_manifest_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:querya_desktop/core/extensions/models/extension_manifest.dart';
import 'package:querya_desktop/core/extensions/models/extension_type.dart';

void main() {
group('ExtensionManifest', () {
test('parses valid manifest correctly', () {
final json = {
'id': 'queryahub.clickhouse-driver',
'name': 'ClickHouse Database Driver',
'version': '1.0.0',
'publisher': 'QueryaHub',
'type': 'database_driver',
'engines': {
'querya_desktop': '^0.5.0'
},
'main': 'bin/clickhouse_plugin',
'icon': 'assets/icon.svg',
'description': 'Full support for ClickHouse databases'
};

final manifest = ExtensionManifest.fromJson(json);

expect(manifest.id, 'queryahub.clickhouse-driver');
expect(manifest.name, 'ClickHouse Database Driver');
expect(manifest.version, '1.0.0');
expect(manifest.publisher, 'QueryaHub');
expect(manifest.type, ExtensionType.databaseDriver);
expect(manifest.engines, {'querya_desktop': '^0.5.0'});
expect(manifest.main, 'bin/clickhouse_plugin');
expect(manifest.icon, 'assets/icon.svg');
expect(manifest.description, 'Full support for ClickHouse databases');
});

test('handles missing optional fields', () {
final json = {
'id': 'queryahub.my-theme',
'name': 'My Theme',
'version': '1.0.0',
'publisher': 'QueryaHub',
'type': 'theme',
'engines': {
'querya_desktop': '^0.5.0'
}
};

final manifest = ExtensionManifest.fromJson(json);

expect(manifest.id, 'queryahub.my-theme');
expect(manifest.type, ExtensionType.theme);
expect(manifest.main, isNull);
expect(manifest.icon, isNull);
expect(manifest.description, isNull);
});

test('falls back to unknown type for unrecognized extension types', () {
final json = {
'id': 'queryahub.future-plugin',
'name': 'Future Plugin',
'version': '1.0.0',
'publisher': 'QueryaHub',
'type': 'future_formatter',
'engines': {}
};

final manifest = ExtensionManifest.fromJson(json);

expect(manifest.type, ExtensionType.unknown);
});

test('throws type error on completely invalid json structure', () {
final json = {
'id': 'missing_everything_else'
};

expect(() => ExtensionManifest.fromJson(json), throwsA(isA<TypeError>()));
});
});
}
Loading