diff --git a/CHANGELOG.md b/CHANGELOG.md
index b0720909..b72a84b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [0.4.14] - 2026-08-30
+
+Near-instantaneous menubar dropdowns, native macOS PlatformMenuBar integration, destructive query confirmation safety modals, comprehensive memory optimizations (string interning, compact storage), grid keyboard navigation, rich cell inspector, and driver resilience hardening.
+
+### Added
+
+- **Instantaneous Menubar Dropdowns (#672, #673)** — Accelerated dropdown open animation to 60ms with `Curves.easeOutCubic`, eliminated initial pointer-down delay, unhindered titlebar dragging from gesture arena interference, and added toggle-close support.
+- **Native macOS PlatformMenuBar Integration (#612, #626)** — Full native macOS top system menu integration with standard application, file, edit, view, and window submenus.
+- **Destructive Query Confirmation Modal (#608, #622, #638, #641)** — Safety confirmation dialog for high-risk DDL and DML operations (`DROP`, `TRUNCATE`, bulk `DELETE`/`UPDATE` without `WHERE`) with SQL preview, detected operations list, and explicit acknowledgement check for core and extension workspaces.
+- **Full Data Grid Keyboard Navigation (#662, #664)** — Seamless keyboard traversal across virtual grid cells using arrow keys, Tab/Shift+Tab, Enter to commit, and Escape to cancel.
+- **Rich Cell Inspector Dialog (#663, #665)** — Dedicated multi-format modal inspector with tabbed views for raw text, JSON, XML/HTML, and binary/hex with word wrap toggle and copy actions.
+- **Binary & BLOB Support in DML (#658, #661)** — Added full support for binary BLOB data in cell editing, DML staging buffer, and dialect-specific SQL generation (`X'...'`, `\x...`).
+- **Extension Driver Crash Recovery & Restart UI (#667, #669)** — Visual driver crash banner with automatic heartbeat monitoring and one-click manual driver restart button.
+- **Interactive Welcome Tour Expansion (#606, #624)** — Expanded onboarding guide with 6 interactive steps, keyboard shortcuts reference matrix, and 1-click sample sandbox setup.
+- **Bi-Directional Navigation (#630, #635)** — Quick navigation links between database overview statistics, home view, and active tables.
+- **Active Selection Sync to Tree (#633, #637)** — Synchronized active table and view selection in tabs with the connections sidebar tree.
+- **Extension Driver SDUI Tree Selection Highlight (#639, #642)** — Visual selection highlighting for SDUI trees rendered by extension drivers.
+- **Querya Extension Driver Mutation Standard (#563, #628)** — Formalized mutation standard specification and test suite for extension drivers.
+
+### Changed & Performance
+
+- **String Interning Pool (#604, #625)** — Deduplicated low-cardinality string allocations in query result grids, slashing heap allocations during large dataset exploration.
+- **Compact Typed Storage & Lazy Cell Stringification (#605, #627)** — Compact memory representation for primitive column vectors, deferring string conversions until render.
+- **Schwartzian Transform Grid Sorting (#602, #615)** — Precomputed sort keys in `sortResultGridRows` to eliminate redundant comparisons during column header sorts.
+- **QuickSelect Median Calculation (#603, #616)** — O(N) median computation in `GridSelectionCalcEngine` instead of O(N log N) sorting.
+- **Background Selection Calculation Offloading (#652, #655)** — Offloaded massive cell selection statistics calculations to background compute workers to keep the UI at 60 FPS.
+- **DataGrid Filter Bar Debouncing (#651, #654)** — Debounced keystroke evaluation in the filter bar to avoid stutter during rapid filter typing.
+- **ResultsTab Filtering Memoization (#650, #653)** — Cached filtered row indices when filter predicates and dataset rows remain unchanged across rebuilds.
+- **Modularized Shared SQL Editor & Workbench (#646, #649)** — Clean modular decoupling of SQL editor tabs, query runners, and workbench state.
+- **Inverted Core Imports Cleanup (#644, #647)** — Eliminated circular/inverted core-to-feature dependencies and cleanly extracted connection models to core.
+- **App Lifecycle & Dispose Resource Cleanup (#670, #671)** — Hardened teardown of timer subscriptions, workers, and active sockets upon window closure.
+- **Connection Pool Delay Tuning (#597, #601)** — Reduced idle connection disposal delay to 4 seconds for rapid tab switching.
+
+### Fixed
+
+- **In-Memory Secret Scrubbing (#607, #623)** — Zeroed in-memory buffers for database passwords and sensitive connection parameters immediately after authentication.
+- **Redis Safe Disconnect & Socket Resilience (#657, #660)** — Protected Redis clients against unhandled socket close exceptions and connection teardown race conditions.
+- **SQLite WAL Mode & Busy Timeout (#611, #617, #656, #659)** — Enabled Write-Ahead Logging (WAL) and 5000ms busy timeout in LocalDb and SQLite workspaces to eliminate database lock errors.
+- **LocalDb Single-Flight Initialization (#645, #648)** — Guarded `LocalDb._open` against concurrent re-initialization race conditions.
+- **Plugin JSON-RPC Bridge Resilience (#666, #668)** — Added heartbeat ping-pong, buffered message queues, and graceful restart on extension pipe drops.
+- **SQL History Monotonic Ordering & Pruning (#629)** — Guaranteed monotonic ordering by auto-incrementing ID in query history queries and batch prune operations.
+- **Literal 'NULL' vs Database NULL Differentiation (#595, #599)** — Fixed DML preview and staging buffer improperly treating the literal text `'NULL'` as SQL `NULL`.
+- **DML Leading Zeroes Preservation (#594, #598)** — Prevented numeric string values (such as postal codes or phone numbers with leading zeroes) from being coerced to integers in generated DML.
+- **Missing Primary Key Warning in DML (#596, #600)** — Displayed explicit duplicate key warnings in the DML preview modal for tables lacking primary keys.
+- **Motion Wobble Harmonization (#631, #634)** — Harmonized workspace transition curves to eliminate dual-axis wobble during connection switching.
+- **MongoDB & Redis Breadcrumbs (#632, #636)** — Preserved persistent breadcrumbs and smooth transitions during NoSQL key and collection navigation.
+- **Focus Traversal in Connection Forms (#610, #621)** — Configured explicit `FocusTraversalGroup`s across multi-field connection dialogs.
+- **Light Theme Accent Contrast (#609, #619)** — Enhanced light theme accent and ring contrast ratios for WCAG compliance.
+- **Monospace Font Stack (#613, #618)** — Added Cascadia Code and Consolas to default monospace font fallback list.
+- **Linux GDK Log Spam (#614, #620)** — Suppressed benign synthetic pointer and cursor theme GDK warnings in Linux console logs.
+
+
## [0.4.13] - 2026-08-25
Production-ready UI polish, complete interactive Data Grid editing suite, advanced query filtering, fluid collapsible navigation, and platform hardening.
diff --git a/docker/clickhouse/init/01_seed.sql b/docker/clickhouse/init/01_seed.sql
index 24c06eec..9bf35a3a 100644
--- a/docker/clickhouse/init/01_seed.sql
+++ b/docker/clickhouse/init/01_seed.sql
@@ -9,6 +9,8 @@ CREATE TABLE IF NOT EXISTS querya.customers
name String,
email String,
city LowCardinality(String),
+ country LowCardinality(String),
+ is_vip Bool,
created_at DateTime
)
ENGINE = MergeTree
@@ -20,7 +22,10 @@ CREATE TABLE IF NOT EXISTS querya.products
sku String,
title String,
category LowCardinality(String),
- price Decimal(10, 2)
+ price Decimal(10, 2),
+ cost Decimal(10, 2),
+ stock UInt32,
+ tags Array(String)
)
ENGINE = MergeTree
ORDER BY id;
@@ -31,6 +36,7 @@ CREATE TABLE IF NOT EXISTS querya.orders
customer_id UInt32,
status LowCardinality(String),
total Decimal(12, 2),
+ discount Decimal(5, 2),
placed_at DateTime
)
ENGINE = MergeTree
@@ -49,17 +55,84 @@ ORDER BY (order_id, product_id);
CREATE TABLE IF NOT EXISTS querya.events
(
event_id UUID,
- event_time DateTime,
+ event_time DateTime64(3),
user_id UInt32,
event_type LowCardinality(String),
path String,
country LowCardinality(String),
+ ip_v4 IPv4,
+ properties Map(String, String),
revenue Decimal(12, 4)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, user_id);
+-- Comprehensive ClickHouse Data Types Table
+CREATE TABLE IF NOT EXISTS querya.all_clickhouse_types
+(
+ id UInt32,
+ col_int8 Int8,
+ col_int16 Int16,
+ col_int32 Int32,
+ col_int64 Int64,
+ col_uint8 UInt8,
+ col_uint16 UInt16,
+ col_uint32 UInt32,
+ col_uint64 UInt64,
+ col_float32 Float32,
+ col_float64 Float64,
+ col_decimal Decimal(18, 4),
+ col_string String,
+ col_fixed_string FixedString(8),
+ col_low_card LowCardinality(String),
+ col_date Date,
+ col_date32 Date32,
+ col_datetime DateTime,
+ col_datetime64 DateTime64(3),
+ col_uuid UUID,
+ col_ipv4 IPv4,
+ col_ipv6 IPv6,
+ col_enum Enum8('alpha' = 1, 'beta' = 2, 'gamma' = 3),
+ col_bool Bool,
+ col_array_str Array(String),
+ col_array_int Array(Int32),
+ col_tuple Tuple(title String, count UInt16),
+ col_map Map(String, String),
+ col_nullable_str Nullable(String),
+ col_nullable_int Nullable(Int32)
+)
+ENGINE = MergeTree
+ORDER BY id;
+
+INSERT INTO querya.all_clickhouse_types VALUES
+(
+ 1, -128, -32768, -2147483648, -9223372036854775808,
+ 255, 65535, 4294967295, 18446744073709551615,
+ 3.14159, 2.718281828459045, 1234567890.1234,
+ 'ClickHouse String with emoji 🚀', 'CLICKHSE', 'Berlin',
+ '2026-08-26', '2026-08-26', '2026-08-26 10:30:00', '2026-08-26 10:30:00.123',
+ 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', '192.168.1.1', '2001:db8::1',
+ 'alpha', true,
+ ['one', 'two', 'three'], [10, 20, 30],
+ ('Tuple Example', 42),
+ map('env', 'production', 'tier', 'gold'),
+ 'Non-null string', 100
+),
+(
+ 2, 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0.0, 0.0, 0.0000,
+ '', '00000000', 'London',
+ '1970-01-01', '1970-01-01', '1970-01-01 00:00:00', '1970-01-01 00:00:00.000',
+ '00000000-0000-0000-0000-000000000000', '0.0.0.0', '::',
+ 'beta', false,
+ [], [],
+ ('', 0),
+ map(),
+ NULL, NULL
+);
+
-- 500 customers
INSERT INTO querya.customers
SELECT
@@ -67,6 +140,8 @@ SELECT
concat('Customer ', toString(number + 1)) AS name,
concat('user', toString(number + 1), '@example.com') AS email,
['Berlin', 'London', 'Madrid', 'Paris', 'Tokyo', 'New York', 'São Paulo', 'Cairo'][number % 8 + 1] AS city,
+ ['DEU', 'GBR', 'ESP', 'FRA', 'JPN', 'USA', 'BRA', 'EGY'][number % 8 + 1] AS country,
+ (number % 5 = 0) AS is_vip,
now() - toIntervalDay(number % 365) AS created_at
FROM numbers(500);
@@ -77,7 +152,10 @@ SELECT
concat('SKU-', leftPad(toString(number + 1), 4, '0')) AS sku,
concat('Product ', toString(number + 1)) AS title,
['Electronics', 'Books', 'Home', 'Sports', 'Fashion'][number % 5 + 1] AS category,
- toDecimal64(round(4.99 + (number % 200) * 1.37, 2), 2) AS price
+ toDecimal64(round(4.99 + (number % 200) * 1.37, 2), 2) AS price,
+ toDecimal64(round(2.00 + (number % 100) * 0.95, 2), 2) AS cost,
+ toUInt32(number * 12 % 300) AS stock,
+ ['popular', 'new_arrival', 'sale'] AS tags
FROM numbers(80);
-- 2_000 orders
@@ -87,6 +165,7 @@ SELECT
toUInt32((number % 500) + 1) AS customer_id,
['new', 'paid', 'shipped', 'cancelled', 'refunded'][number % 5 + 1] AS status,
toDecimal64(round(9.99 + (number % 150) * 2.41, 2), 2) AS total,
+ toDecimal64(round((number % 10) * 1.5, 2), 2) AS discount,
now() - toIntervalHour(number % (24 * 120)) AS placed_at
FROM numbers(2000);
@@ -99,14 +178,16 @@ SELECT
toDecimal64(round(4.99 + (number % 200) * 1.37, 2), 2) AS unit_price
FROM numbers(6000);
--- 50_000 analytics events
+-- 10_000 events
INSERT INTO querya.events
SELECT
generateUUIDv4() AS event_id,
- now() - toIntervalSecond(number % (86400 * 30)) AS event_time,
+ now64(3) - toIntervalMinute(number % (60 * 24 * 30)) AS event_time,
toUInt32((number % 500) + 1) AS user_id,
- ['page_view', 'add_to_cart', 'purchase', 'search', 'login'][number % 5 + 1] AS event_type,
- concat('/app/', ['home', 'catalog', 'product', 'checkout', 'account'][number % 5 + 1]) AS path,
- ['DE', 'GB', 'ES', 'FR', 'JP', 'US', 'BR', 'EG'][number % 8 + 1] AS country,
- if(number % 5 = 2, toDecimal64(round((number % 100) * 1.25, 4), 4), toDecimal64(0, 4)) AS revenue
-FROM numbers(50000);
+ ['pageview', 'click', 'scroll', 'purchase', 'add_to_cart'][number % 5 + 1] AS event_type,
+ ['/home', '/products', '/checkout', '/cart', '/profile'][number % 5 + 1] AS path,
+ ['DEU', 'USA', 'GBR', 'FRA', 'JPN', 'CAN'][number % 6 + 1] AS country,
+ toIPv4(concat('192.168.1.', toString(1 + (number % 254)))) AS ip_v4,
+ map('browser', 'Chrome', 'version', '128.0') AS properties,
+ toDecimal64(round((number % 50) * 1.25, 4), 4) AS revenue
+FROM numbers(10000);
diff --git a/docker/mongo/init/01_seed.js b/docker/mongo/init/01_seed.js
index b7dd34a8..ec75c14c 100644
--- a/docker/mongo/init/01_seed.js
+++ b/docker/mongo/init/01_seed.js
@@ -1,83 +1,131 @@
-// Demo data for Querya manual testing (MongoDB).
+// Demo data for Querya MongoDB testing.
const appDb = db.getSiblingDB('querya');
appDb.users.drop();
appDb.products.drop();
appDb.orders.drop();
+appDb.types_showcase.drop();
-appDb.users.insertMany([
+// 1. All BSON Types Showcase Collection
+appDb.types_showcase.insertMany([
{
- name: 'Alice Martin',
- email: 'alice@example.com',
- role: 'admin',
- city: 'Berlin',
- tags: ['staff', 'beta'],
- active: true,
- },
- {
- name: 'Bob Smith',
- email: 'bob@example.com',
- role: 'customer',
- city: 'London',
- tags: ['beta'],
- active: true,
+ _id: new ObjectId(),
+ title: 'Rich BSON Document 1',
+ col_string: 'MongoDB BSON String with emoji 🚀 🍃',
+ col_int32: NumberInt(2147483647),
+ col_int64: NumberLong('9223372036854775807'),
+ col_double: 3.141592653589793,
+ col_decimal: NumberDecimal('12345678901234.5678'),
+ col_boolean: true,
+ col_date: new Date('2026-08-26T10:30:00Z'),
+ col_array_primitives: [1, 2, 3, 42, 99],
+ col_array_strings: ['Alpha', 'Beta', 'Gamma'],
+ col_array_objects: [
+ { id: 1, label: 'First' },
+ { id: 2, label: 'Second' }
+ ],
+ col_object: {
+ nested_key: 'nested_value',
+ level_2: {
+ deep: true,
+ count: NumberInt(10)
+ }
+ },
+ col_binary: new BinData(0, '3q2+7w=='),
+ col_regex: /^querya/i,
+ col_null: null
},
{
- name: 'Carla Ruiz',
- email: 'carla@example.com',
- role: 'customer',
- city: 'Madrid',
- tags: [],
- active: false,
- },
+ _id: new ObjectId(),
+ title: 'Boundary & Min Values',
+ col_string: '',
+ col_int32: NumberInt(-2147483648),
+ col_int64: NumberLong('-9223372036854775808'),
+ col_double: -2.71828,
+ col_decimal: NumberDecimal('-999999.99'),
+ col_boolean: false,
+ col_date: new Date('1970-01-01T00:00:00Z'),
+ col_array_primitives: [],
+ col_array_strings: [],
+ col_array_objects: [],
+ col_object: {},
+ col_binary: new BinData(0, ''),
+ col_regex: /.+/,
+ col_null: null
+ }
]);
-appDb.products.insertMany([
- { sku: 'SKU-001', title: 'Wireless Mouse', price: 29.99, stock: 120 },
- { sku: 'SKU-002', title: 'Mechanical Keyboard', price: 89.0, stock: 45 },
- { sku: 'SKU-003', title: 'USB-C Hub', price: 45.5, stock: 80 },
- { sku: 'SKU-004', title: '27" Monitor', price: 329.0, stock: 15 },
-]);
+// 2. Generate 100 Users
+const users = [];
+const cities = ['Berlin', 'London', 'Madrid', 'Paris', 'Tokyo', 'New York', 'San Francisco', 'Sydney', 'Toronto', 'Singapore'];
+const countries = ['DEU', 'GBR', 'ESP', 'FRA', 'JPN', 'USA', 'USA', 'AUS', 'CAN', 'SGP'];
-appDb.orders.insertMany([
- {
- customerEmail: 'alice@example.com',
- status: 'paid',
- total: 164.49,
- placedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
+for (let i = 1; i <= 100; i++) {
+ users.push({
+ userId: i,
+ name: 'Customer ' + i,
+ email: 'customer' + i + '@example.org',
+ city: cities[(i - 1) % cities.length],
+ country: countries[(i - 1) % countries.length],
+ role: (i % 5 === 0) ? 'admin' : (i % 3 === 0 ? 'editor' : 'customer'),
+ active: (i % 15 !== 0),
+ tags: (i % 2 === 0) ? ['vip', 'tech'] : ['standard'],
+ metadata: {
+ loyaltyTier: (i % 5 === 0) ? 'gold' : (i % 3 === 0 ? 'silver' : 'bronze'),
+ score: i * 15
+ },
+ createdAt: new Date(Date.now() - i * 24 * 60 * 60 * 1000)
+ });
+}
+appDb.users.insertMany(users);
+
+// 3. Generate 50 Products
+const products = [];
+const categories = ['Peripherals', 'Hardware', 'Audio', 'Furniture', 'Accessories'];
+for (let i = 1; i <= 50; i++) {
+ products.push({
+ sku: 'SKU-' + String(i).padStart(4, '0'),
+ title: 'Product ' + i,
+ category: categories[(i - 1) % categories.length],
+ price: NumberDecimal((19.99 + i * 7.45).toFixed(2)),
+ stock: NumberInt((i * 15) % 250),
+ tags: ['electronics', 'gadget'],
+ createdAt: new Date(Date.now() - i * 24 * 60 * 60 * 1000)
+ });
+}
+appDb.products.insertMany(products);
+
+// 4. Generate 250 Orders
+const orders = [];
+const statuses = ['new', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded'];
+for (let i = 1; i <= 250; i++) {
+ orders.push({
+ orderId: i,
+ customerEmail: 'customer' + (1 + (i % 100)) + '@example.org',
+ status: statuses[(i - 1) % statuses.length],
+ total: NumberDecimal((49.99 + i * 3.15).toFixed(2)),
+ placedAt: new Date(Date.now() - (250 - i) * 60 * 60 * 1000),
lines: [
- { sku: 'SKU-001', qty: 1, unitPrice: 29.99 },
- { sku: 'SKU-003', qty: 1, unitPrice: 45.5 },
- { sku: 'SKU-002', qty: 1, unitPrice: 89.0 },
- ],
- },
- {
- customerEmail: 'bob@example.com',
- status: 'shipped',
- total: 404.0,
- placedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000),
- lines: [{ sku: 'SKU-004', qty: 1, unitPrice: 329.0 }],
- },
- {
- customerEmail: 'carla@example.com',
- status: 'new',
- total: 29.99,
- placedAt: new Date(),
- lines: [{ sku: 'SKU-001', qty: 1, unitPrice: 29.99 }],
- },
-]);
+ { sku: 'SKU-0001', qty: 1, unitPrice: NumberDecimal('29.99') },
+ { sku: 'SKU-0002', qty: 2, unitPrice: NumberDecimal('45.50') }
+ ]
+ });
+}
+appDb.orders.insertMany(orders);
appDb.users.createIndex({ email: 1 }, { unique: true });
appDb.products.createIndex({ sku: 1 }, { unique: true });
appDb.orders.createIndex({ status: 1, placedAt: -1 });
+// Analytics Database
const analyticsDb = db.getSiblingDB('analytics');
analyticsDb.metrics.drop();
-analyticsDb.metrics.insertMany([
- { day: new Date(), orders: 4, revenue: 203.99 },
- {
- day: new Date(Date.now() - 24 * 60 * 60 * 1000),
- orders: 9,
- revenue: 615.0,
- },
-]);
+const metrics = [];
+for (let i = 1; i <= 60; i++) {
+ metrics.push({
+ day: new Date(Date.now() - (60 - i) * 24 * 60 * 60 * 1000),
+ orders: NumberInt(10 + (i * 3) % 40),
+ revenue: NumberDecimal((500.00 + i * 35.50).toFixed(2))
+ });
+}
+analyticsDb.metrics.insertMany(metrics);
diff --git a/docker/mysql/init/01_shop.sql b/docker/mysql/init/01_shop.sql
index 3cd98457..a5df51b0 100644
--- a/docker/mysql/init/01_shop.sql
+++ b/docker/mysql/init/01_shop.sql
@@ -1,87 +1,137 @@
--- Demo schema for Querya manual testing (MySQL / MariaDB-compatible).
+-- Demo schema for Querya manual testing (MySQL 8.4+).
USE querya;
-CREATE TABLE customers (
+CREATE TABLE IF NOT EXISTS customers (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(120) NOT NULL,
email VARCHAR(160) NOT NULL UNIQUE,
city VARCHAR(80),
+ country VARCHAR(3) DEFAULT 'USA',
+ is_vip BOOLEAN DEFAULT FALSE,
+ metadata JSON,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
-CREATE TABLE products (
+CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
sku VARCHAR(32) NOT NULL UNIQUE,
title VARCHAR(160) NOT NULL,
- price DECIMAL(10, 2) NOT NULL
+ category VARCHAR(64) NOT NULL DEFAULT 'General',
+ price DECIMAL(10, 2) NOT NULL,
+ cost DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
+ stock INT NOT NULL DEFAULT 0,
+ is_active BOOLEAN NOT NULL DEFAULT TRUE,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
-CREATE TABLE orders (
+CREATE TABLE IF NOT EXISTS orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
- status VARCHAR(32) NOT NULL DEFAULT 'new',
+ status ENUM('new', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded') NOT NULL DEFAULT 'new',
total DECIMAL(10, 2) NOT NULL DEFAULT 0,
+ discount DECIMAL(5, 2) NOT NULL DEFAULT 0,
+ shipping_address JSON,
placed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ delivered_at TIMESTAMP NULL,
CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers (id)
) ENGINE=InnoDB;
-CREATE TABLE order_lines (
+CREATE TABLE IF NOT EXISTS order_lines (
order_id INT NOT NULL,
product_id INT NOT NULL,
qty INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL,
+ discount_applied DECIMAL(5, 2) NOT NULL DEFAULT 0,
PRIMARY KEY (order_id, product_id),
CONSTRAINT fk_lines_order FOREIGN KEY (order_id) REFERENCES orders (id) ON DELETE CASCADE,
CONSTRAINT fk_lines_product FOREIGN KEY (product_id) REFERENCES products (id)
) ENGINE=InnoDB;
-INSERT INTO customers (name, email, city) VALUES
- ('Alice Martin', 'alice@example.com', 'Berlin'),
- ('Bob Smith', 'bob@example.com', 'London'),
- ('Carla Ruiz', 'carla@example.com', 'Madrid');
+-- Stored procedure to generate seed data in MySQL
+DELIMITER //
+CREATE PROCEDURE SeedShopData()
+BEGIN
+ DECLARE i INT DEFAULT 1;
+ DECLARE cities VARCHAR(500) DEFAULT 'Berlin,London,Madrid,Paris,Tokyo,New York,San Francisco,Sydney,Toronto,Singapore';
+
+ -- Seed 100 customers
+ WHILE i <= 100 DO
+ INSERT INTO customers (name, email, city, country, is_vip, metadata, created_at)
+ VALUES (
+ CONCAT('Customer ', i),
+ CONCAT('customer', i, '@example.org'),
+ ELT(1 + (i MOD 10), 'Berlin', 'London', 'Madrid', 'Paris', 'Tokyo', 'New York', 'San Francisco', 'Sydney', 'Toronto', 'Singapore'),
+ ELT(1 + (i MOD 10), 'DEU', 'GBR', 'ESP', 'FRA', 'JPN', 'USA', 'USA', 'AUS', 'CAN', 'SGP'),
+ (i MOD 5 = 0),
+ JSON_OBJECT('loyalty_tier', IF(i MOD 5 = 0, 'gold', IF(i MOD 3 = 0, 'silver', 'bronze')), 'points', i * 42),
+ DATE_SUB(NOW(), INTERVAL i DAY)
+ );
+ SET i = i + 1;
+ END WHILE;
+
+ -- Seed 50 products
+ SET i = 1;
+ WHILE i <= 50 DO
+ INSERT INTO products (sku, title, category, price, cost, stock, is_active, created_at)
+ VALUES (
+ CONCAT('SKU-', LPAD(i, 4, '0')),
+ CONCAT(ELT(1 + (i MOD 10), 'Wireless Mouse', 'Mechanical Keyboard', 'USB-C Hub', '27" 4K Monitor', 'Headphones Pro', 'Desk Chair', 'Webcam 1080p', 'Microphone', 'Laptop Stand', 'Desk Lamp'), ' v', (i DIV 10) + 1),
+ ELT(1 + (i MOD 5), 'Peripherals', 'Hardware', 'Audio', 'Furniture', 'Accessories'),
+ ROUND(19.99 + (i * 7.45), 2),
+ ROUND(10.00 + (i * 4.20), 2),
+ (i * 15) MOD 250,
+ (i MOD 15 != 0),
+ DATE_SUB(NOW(), INTERVAL i DAY)
+ );
+ SET i = i + 1;
+ END WHILE;
-INSERT INTO products (sku, title, price) VALUES
- ('SKU-001', 'Wireless Mouse', 29.99),
- ('SKU-002', 'Mechanical Keyboard', 89.00),
- ('SKU-003', 'USB-C Hub', 45.50),
- ('SKU-004', '27 inch Monitor', 329.00);
+ -- Seed 250 orders
+ SET i = 1;
+ WHILE i <= 250 DO
+ INSERT INTO orders (customer_id, status, total, discount, shipping_address, placed_at, delivered_at)
+ VALUES (
+ 1 + (i MOD 100),
+ ELT(1 + (i MOD 6), 'new', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded'),
+ ROUND(49.99 + (i * 3.15), 2),
+ ROUND((i MOD 10) * 1.5, 2),
+ JSON_OBJECT('street', CONCAT(i, ' Main St'), 'zip', CONCAT('1000', i MOD 90)),
+ DATE_SUB(NOW(), INTERVAL (250 - i) HOUR),
+ IF(i MOD 6 IN (3, 4), DATE_SUB(NOW(), INTERVAL (250 - i - 24) HOUR), NULL)
+ );
+ SET i = i + 1;
+ END WHILE;
-INSERT INTO orders (customer_id, status, total, placed_at) VALUES
- (1, 'paid', 164.49, NOW() - INTERVAL 2 DAY),
- (2, 'shipped', 404.49, NOW() - INTERVAL 1 DAY),
- (3, 'new', 29.99, NOW());
+ -- Seed 500 order lines
+ SET i = 1;
+ WHILE i <= 500 DO
+ INSERT IGNORE INTO order_lines (order_id, product_id, qty, unit_price, discount_applied)
+ VALUES (
+ 1 + (i MOD 250),
+ 1 + ((i * 7) MOD 50),
+ 1 + (i MOD 5),
+ ROUND(19.99 + ((1 + ((i * 7) MOD 50)) * 7.45), 2),
+ 0.00
+ );
+ SET i = i + 1;
+ END WHILE;
+END //
+DELIMITER ;
-INSERT INTO order_lines (order_id, product_id, qty, unit_price) VALUES
- (1, 1, 1, 29.99),
- (1, 3, 1, 45.50),
- (1, 2, 1, 89.00),
- (2, 4, 1, 329.00),
- (2, 1, 1, 29.99),
- (2, 3, 1, 45.50),
- (3, 1, 1, 29.99);
+CALL SeedShopData();
+DROP PROCEDURE SeedShopData;
-CREATE VIEW customer_spending AS
+CREATE OR REPLACE VIEW customer_spending AS
SELECT
c.id,
c.name,
+ c.email,
c.city,
+ c.country,
+ c.is_vip,
COUNT(o.id) AS order_count,
- COALESCE(SUM(o.total), 0) AS lifetime_total
+ COALESCE(SUM(o.total), 0) AS lifetime_total,
+ MAX(o.placed_at) AS last_order_date
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
-GROUP BY c.id, c.name, c.city;
-
-CREATE DATABASE IF NOT EXISTS analytics;
-
-USE analytics;
-
-CREATE TABLE daily_sales (
- day DATE PRIMARY KEY,
- orders INT NOT NULL,
- revenue DECIMAL(12, 2) NOT NULL
-) ENGINE=InnoDB;
-
-INSERT INTO daily_sales (day, orders, revenue) VALUES
- (CURDATE() - INTERVAL 2 DAY, 12, 842.50),
- (CURDATE() - INTERVAL 1 DAY, 9, 615.00),
- (CURDATE(), 4, 203.99);
+GROUP BY c.id, c.name, c.email, c.city, c.country, c.is_vip;
diff --git a/docker/mysql/init/02_all_types.sql b/docker/mysql/init/02_all_types.sql
new file mode 100644
index 00000000..f2069593
--- /dev/null
+++ b/docker/mysql/init/02_all_types.sql
@@ -0,0 +1,99 @@
+USE querya;
+
+-- Comprehensive MySQL Data Types Showcase Table
+CREATE TABLE IF NOT EXISTS all_mysql_types (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ col_tinyint TINYINT,
+ col_smallint SMALLINT,
+ col_mediumint MEDIUMINT,
+ col_int INT,
+ col_bigint BIGINT,
+ col_decimal DECIMAL(12, 4),
+ col_float FLOAT,
+ col_double DOUBLE,
+ col_bit BIT(8),
+ col_boolean BOOLEAN,
+ col_char CHAR(10),
+ col_varchar VARCHAR(255),
+ col_text TEXT,
+ col_json JSON,
+ col_date DATE,
+ col_time TIME,
+ col_datetime DATETIME,
+ col_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ col_year YEAR,
+ col_enum ENUM('alpha', 'beta', 'gamma', 'delta') DEFAULT 'alpha',
+ col_set SET('read', 'write', 'execute', 'admin') DEFAULT 'read',
+ col_binary BINARY(16),
+ col_varbinary VARBINARY(64),
+ col_blob BLOB,
+ description VARCHAR(100)
+) ENGINE=InnoDB;
+
+INSERT INTO all_mysql_types (
+ col_tinyint, col_smallint, col_mediumint, col_int, col_bigint,
+ col_decimal, col_float, col_double, col_bit, col_boolean,
+ col_char, col_varchar, col_text, col_json,
+ col_date, col_time, col_datetime, col_year,
+ col_enum, col_set, col_binary, col_varbinary, col_blob, description
+) VALUES
+(
+ 127, 32767, 8388607, 2147483647, 9223372036854775807,
+ 12345678.9012, 3.14159, 2.718281828459045, b'10101010', TRUE,
+ 'FIXED', 'Variable length string test', 'Long multiline text paragraph with emoji 🚀 🐬',
+ JSON_OBJECT('name', 'Querya MySQL', 'version', '8.4', 'features', JSON_ARRAY('DataGrid', 'Inspector', 'Calc')),
+ '2026-08-26', '10:30:00', '2026-08-26 10:30:00', 2026,
+ 'alpha', 'read,write',
+ UNHEX('DEADBEEFCAFE0102030405060708090A'), UNHEX('CAFEBABE'), 'Binary BLOB payload', 'Max / Standard row'
+),
+(
+ -128, -32768, -8388608, -2147483648, -9223372036854775808,
+ -12345678.9012, -3.14159, -2.718281828459045, b'00000000', FALSE,
+ 'MIN', 'Negative boundaries', 'Negative numbers test',
+ JSON_ARRAY('one', 'two', 'three'),
+ '1970-01-01', '00:00:00', '1970-01-01 00:00:00', 1970,
+ 'gamma', 'execute,admin',
+ UNHEX('00000000000000000000000000000000'), UNHEX('00'), 'Min bytes', 'Min / Boundary row'
+),
+(
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, 'All NULL row'
+);
+
+-- Second database: analytics
+CREATE DATABASE IF NOT EXISTS analytics;
+USE analytics;
+
+CREATE TABLE IF NOT EXISTS daily_sales (
+ day DATE PRIMARY KEY,
+ orders INT NOT NULL,
+ gross_revenue DECIMAL(12, 2) NOT NULL,
+ net_revenue DECIMAL(12, 2) NOT NULL,
+ refunds DECIMAL(12, 2) NOT NULL DEFAULT 0.00,
+ new_customers INT NOT NULL DEFAULT 0
+) ENGINE=InnoDB;
+
+DELIMITER //
+CREATE PROCEDURE SeedDailySales()
+BEGIN
+ DECLARE i INT DEFAULT 1;
+ WHILE i <= 90 DO
+ INSERT IGNORE INTO daily_sales (day, orders, gross_revenue, net_revenue, refunds, new_customers)
+ VALUES (
+ DATE_SUB(CURDATE(), INTERVAL (90 - i) DAY),
+ 10 + (i * 3) MOD 45,
+ ROUND(500.00 + (i * 45.20) + ((i MOD 7) * 120.00), 2),
+ ROUND(450.00 + (i * 40.00) + ((i MOD 7) * 110.00), 2),
+ ROUND((i MOD 5) * 25.50, 2),
+ 2 + (i MOD 12)
+ );
+ SET i = i + 1;
+ END WHILE;
+END //
+DELIMITER ;
+
+CALL SeedDailySales();
+DROP PROCEDURE SeedDailySales;
diff --git a/docker/postgres/init/01_shop.sql b/docker/postgres/init/01_shop.sql
index bc540a53..0ccfe8b9 100644
--- a/docker/postgres/init/01_shop.sql
+++ b/docker/postgres/init/01_shop.sql
@@ -1,73 +1,130 @@
--- Demo schema for Querya manual testing (PostgreSQL).
+-- Demo schema for Querya testing (PostgreSQL).
CREATE SCHEMA IF NOT EXISTS shop;
-CREATE TABLE shop.customers (
+CREATE TABLE IF NOT EXISTS shop.customers (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
city TEXT,
+ country VARCHAR(3) DEFAULT 'USA',
+ is_vip BOOLEAN DEFAULT FALSE,
+ metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-CREATE TABLE shop.products (
+CREATE TABLE IF NOT EXISTS shop.products (
id SERIAL PRIMARY KEY,
- sku TEXT NOT NULL UNIQUE,
+ sku VARCHAR(32) NOT NULL UNIQUE,
title TEXT NOT NULL,
- price NUMERIC(10, 2) NOT NULL CHECK (price >= 0)
+ category TEXT NOT NULL DEFAULT 'General',
+ price NUMERIC(10, 2) NOT NULL CHECK (price >= 0),
+ cost NUMERIC(10, 2) NOT NULL DEFAULT 0.00,
+ stock INT NOT NULL DEFAULT 0,
+ tags TEXT[] DEFAULT '{}',
+ specs JSONB DEFAULT '{}',
+ is_active BOOLEAN NOT NULL DEFAULT TRUE,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-CREATE TABLE shop.orders (
+CREATE TABLE IF NOT EXISTS shop.orders (
id SERIAL PRIMARY KEY,
customer_id INT NOT NULL REFERENCES shop.customers (id),
status TEXT NOT NULL DEFAULT 'new',
total NUMERIC(10, 2) NOT NULL DEFAULT 0,
- placed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+ discount NUMERIC(5, 2) NOT NULL DEFAULT 0,
+ shipping_address JSONB DEFAULT '{}',
+ placed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ delivered_at TIMESTAMPTZ
);
-CREATE TABLE shop.order_lines (
+CREATE TABLE IF NOT EXISTS shop.order_lines (
order_id INT NOT NULL REFERENCES shop.orders (id) ON DELETE CASCADE,
product_id INT NOT NULL REFERENCES shop.products (id),
qty INT NOT NULL CHECK (qty > 0),
unit_price NUMERIC(10, 2) NOT NULL,
+ discount_applied NUMERIC(5, 2) NOT NULL DEFAULT 0,
PRIMARY KEY (order_id, product_id)
);
-INSERT INTO shop.customers (name, email, city) VALUES
- ('Alice Martin', 'alice@example.com', 'Berlin'),
- ('Bob Smith', 'bob@example.com', 'London'),
- ('Carla Ruiz', 'carla@example.com', 'Madrid');
+-- Seed 100 customers
+INSERT INTO shop.customers (name, email, city, country, is_vip, metadata, created_at)
+SELECT
+ 'Customer ' || i,
+ 'customer' || i || '@example.' || (CASE i % 4 WHEN 0 THEN 'com' WHEN 1 THEN 'org' WHEN 2 THEN 'io' ELSE 'net' END),
+ (ARRAY['Berlin', 'London', 'Madrid', 'Paris', 'Tokyo', 'New York', 'San Francisco', 'Sydney', 'Toronto', 'Singapore'])[1 + (i % 10)],
+ (ARRAY['DEU', 'GBR', 'ESP', 'FRA', 'JPN', 'USA', 'USA', 'AUS', 'CAN', 'SGP'])[1 + (i % 10)],
+ (i % 5 = 0),
+ jsonb_build_object('loyalty_tier', (CASE WHEN i % 5 = 0 THEN 'gold' WHEN i % 3 = 0 THEN 'silver' ELSE 'bronze' END), 'points', i * 42),
+ NOW() - (i || ' days')::INTERVAL
+FROM generate_series(1, 100) AS i;
-INSERT INTO shop.products (sku, title, price) VALUES
- ('SKU-001', 'Wireless Mouse', 29.99),
- ('SKU-002', 'Mechanical Keyboard', 89.00),
- ('SKU-003', 'USB-C Hub', 45.50),
- ('SKU-004', '27" Monitor', 329.00);
+-- Seed 50 products
+INSERT INTO shop.products (sku, title, category, price, cost, stock, tags, specs, is_active, created_at)
+SELECT
+ 'SKU-' || LPAD(i::TEXT, 4, '0'),
+ (ARRAY['Wireless Mouse', 'Mechanical Keyboard', 'USB-C Hub', '27" 4K Monitor', 'Noise-Canceling Headphones', 'Ergonomic Desk Chair', 'Webcam 1080p', 'Microphone Pro', 'Laptop Stand', 'Smart Desk Lamp'])[1 + (i % 10)] || ' v' || (i / 10 + 1),
+ (ARRAY['Peripherals', 'Hardware', 'Audio', 'Furniture', 'Accessories'])[1 + (i % 5)],
+ ROUND((19.99 + (i * 7.45))::NUMERIC, 2),
+ ROUND((10.00 + (i * 4.20))::NUMERIC, 2),
+ (i * 15) % 250,
+ ARRAY['bestseller', 'tech', (CASE i % 3 WHEN 0 THEN 'wireless' WHEN 1 THEN 'usb' ELSE 'ergonomic' END)],
+ jsonb_build_object('weight_g', 150 + i * 10, 'warranty_months', (CASE WHEN i % 2 = 0 THEN 24 ELSE 12 END), 'color', (ARRAY['black', 'white', 'space_gray', 'silver'])[1 + (i % 4)]),
+ (i % 15 != 0),
+ NOW() - (i || ' days')::INTERVAL
+FROM generate_series(1, 50) AS i;
-INSERT INTO shop.orders (customer_id, status, total, placed_at) VALUES
- (1, 'paid', 164.49, NOW() - INTERVAL '2 days'),
- (2, 'shipped', 404.49, NOW() - INTERVAL '1 day'),
- (3, 'new', 29.99, NOW());
+-- Seed 300 orders
+INSERT INTO shop.orders (customer_id, status, total, discount, shipping_address, placed_at, delivered_at)
+SELECT
+ 1 + (i % 100),
+ (ARRAY['new', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded'])[1 + (i % 6)],
+ ROUND((49.99 + (i * 3.15))::NUMERIC, 2),
+ ROUND(((i % 10) * 1.5)::NUMERIC, 2),
+ jsonb_build_object('street', i || ' Main St', 'zip', '1000' || (i % 90)),
+ NOW() - ((300 - i) || ' hours')::INTERVAL,
+ (CASE WHEN i % 6 IN (3, 4) THEN NOW() - ((300 - i - 24) || ' hours')::INTERVAL ELSE NULL END)
+FROM generate_series(1, 300) AS i;
-INSERT INTO shop.order_lines (order_id, product_id, qty, unit_price) VALUES
- (1, 1, 1, 29.99),
- (1, 3, 1, 45.50),
- (1, 2, 1, 89.00),
- (2, 4, 1, 329.00),
- (2, 1, 1, 29.99),
- (2, 3, 1, 45.50),
- (3, 1, 1, 29.99);
+-- Seed 800 order lines
+INSERT INTO shop.order_lines (order_id, product_id, qty, unit_price, discount_applied)
+SELECT
+ 1 + (i % 300),
+ 1 + ((i * 7) % 50),
+ 1 + (i % 5),
+ ROUND((19.99 + ((1 + ((i * 7) % 50)) * 7.45))::NUMERIC, 2),
+ 0.00
+FROM generate_series(1, 800) AS i
+ON CONFLICT (order_id, product_id) DO NOTHING;
+-- Views
CREATE OR REPLACE VIEW shop.customer_spending AS
SELECT
c.id,
c.name,
+ c.email,
c.city,
+ c.country,
+ c.is_vip,
COUNT(o.id) AS order_count,
- COALESCE(SUM(o.total), 0) AS lifetime_total
+ COALESCE(SUM(o.total), 0) AS lifetime_total,
+ MAX(o.placed_at) AS last_order_date
FROM shop.customers c
LEFT JOIN shop.orders o ON o.customer_id = c.id
-GROUP BY c.id, c.name, c.city;
+GROUP BY c.id, c.name, c.email, c.city, c.country, c.is_vip;
+CREATE MATERIALIZED VIEW IF NOT EXISTS shop.monthly_sales_summary AS
+SELECT
+ DATE_TRUNC('month', o.placed_at)::DATE AS sales_month,
+ COUNT(DISTINCT o.id) AS total_orders,
+ COUNT(DISTINCT o.customer_id) AS unique_customers,
+ SUM(o.total) AS gross_revenue,
+ ROUND(AVG(o.total), 2) AS avg_order_value
+FROM shop.orders o
+WHERE o.status NOT IN ('cancelled', 'refunded')
+GROUP BY DATE_TRUNC('month', o.placed_at)::DATE
+ORDER BY sales_month DESC;
+
+-- Functions
CREATE OR REPLACE FUNCTION shop.order_count_for_customer(p_customer_id INT)
RETURNS INT
LANGUAGE sql
@@ -75,3 +132,21 @@ STABLE
AS $$
SELECT COUNT(*)::INT FROM shop.orders WHERE customer_id = p_customer_id;
$$;
+
+CREATE OR REPLACE FUNCTION shop.get_customer_tier(p_lifetime_total NUMERIC)
+RETURNS TEXT
+LANGUAGE plpgsql
+IMMUTABLE
+AS $$
+BEGIN
+ IF p_lifetime_total >= 5000 THEN
+ RETURN 'PLATINUM';
+ ELSIF p_lifetime_total >= 1000 THEN
+ RETURN 'GOLD';
+ ELSIF p_lifetime_total >= 250 THEN
+ RETURN 'SILVER';
+ ELSE
+ RETURN 'BRONZE';
+ END IF;
+END;
+$$;
diff --git a/docker/postgres/init/02_analytics.sql b/docker/postgres/init/02_analytics.sql
index 9aa868f1..e5c829be 100644
--- a/docker/postgres/init/02_analytics.sql
+++ b/docker/postgres/init/02_analytics.sql
@@ -1,17 +1,52 @@
--- Second database to exercise PostgreSQL tree / database switching.
+-- Second database for multi-DB testing in Querya.
CREATE DATABASE analytics;
\connect analytics
-CREATE SCHEMA metrics;
+CREATE SCHEMA IF NOT EXISTS metrics;
-CREATE TABLE metrics.daily_sales (
+CREATE TABLE IF NOT EXISTS metrics.daily_sales (
day DATE PRIMARY KEY,
orders INT NOT NULL,
- revenue NUMERIC(12, 2) NOT NULL
+ gross_revenue NUMERIC(12, 2) NOT NULL,
+ net_revenue NUMERIC(12, 2) NOT NULL,
+ refunds NUMERIC(12, 2) NOT NULL DEFAULT 0.00,
+ new_customers INT NOT NULL DEFAULT 0
);
-INSERT INTO metrics.daily_sales (day, orders, revenue) VALUES
- (CURRENT_DATE - 2, 12, 842.50),
- (CURRENT_DATE - 1, 9, 615.00),
- (CURRENT_DATE, 4, 203.99);
+CREATE TABLE IF NOT EXISTS metrics.user_events (
+ event_id BIGSERIAL PRIMARY KEY,
+ user_id INT NOT NULL,
+ event_type VARCHAR(64) NOT NULL,
+ page_url TEXT NOT NULL,
+ referrer TEXT,
+ user_agent TEXT,
+ ip_address INET,
+ duration_seconds NUMERIC(8, 2),
+ event_time TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+-- Seed 90 days of daily sales
+INSERT INTO metrics.daily_sales (day, orders, gross_revenue, net_revenue, refunds, new_customers)
+SELECT
+ (CURRENT_DATE - (90 - i)) AS day,
+ 10 + (i * 3) % 45,
+ ROUND((500.00 + (i * 45.20) + ((i % 7) * 120.00))::NUMERIC, 2),
+ ROUND((450.00 + (i * 40.00) + ((i % 7) * 110.00))::NUMERIC, 2),
+ ROUND(((i % 5) * 25.50)::NUMERIC, 2),
+ 2 + (i % 12)
+FROM generate_series(1, 90) AS i
+ON CONFLICT (day) DO NOTHING;
+
+-- Seed 500 user events
+INSERT INTO metrics.user_events (user_id, event_type, page_url, referrer, user_agent, ip_address, duration_seconds, event_time)
+SELECT
+ 1 + (i % 150),
+ (ARRAY['pageview', 'button_click', 'add_to_cart', 'search', 'checkout_start', 'payment_success'])[1 + (i % 6)],
+ (ARRAY['/home', '/products', '/cart', '/checkout', '/account', '/search?q=keyboard', '/pricing'])[1 + (i % 7)],
+ (ARRAY['https://google.com', 'https://github.com', 'https://twitter.com', NULL, 'https://reddit.com'])[1 + (i % 5)],
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko)',
+ ('192.168.1.' || (1 + (i % 254)))::INET,
+ ROUND((1.5 + (i % 60) * 2.3)::NUMERIC, 2),
+ NOW() - ((500 - i) * 10 || ' minutes')::INTERVAL
+FROM generate_series(1, 500) AS i;
diff --git a/docker/postgres/init/03_all_types.sql b/docker/postgres/init/03_all_types.sql
new file mode 100644
index 00000000..fffb7d6a
--- /dev/null
+++ b/docker/postgres/init/03_all_types.sql
@@ -0,0 +1,270 @@
+-- Comprehensive PostgreSQL Data Types Showcase Schema
+\connect querya
+
+DROP SCHEMA IF EXISTS types_showcase CASCADE;
+CREATE SCHEMA types_showcase;
+
+-- 1. Custom User Types (Enums, Domains, Composite Types)
+CREATE TYPE types_showcase.user_role_enum AS ENUM ('admin', 'editor', 'viewer', 'guest');
+CREATE TYPE types_showcase.order_status_enum AS ENUM ('draft', 'pending', 'processing', 'completed', 'cancelled', 'refunded');
+
+CREATE DOMAIN types_showcase.positive_int AS INTEGER CHECK (VALUE > 0);
+CREATE DOMAIN types_showcase.valid_email AS TEXT CHECK (VALUE ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');
+
+CREATE TYPE types_showcase.geo_coordinate AS (
+ latitude DOUBLE PRECISION,
+ longitude DOUBLE PRECISION,
+ altitude REAL
+);
+
+CREATE TYPE types_showcase.address_type AS (
+ street TEXT,
+ city TEXT,
+ postal_code VARCHAR(16),
+ country VARCHAR(3)
+);
+
+-- 2. Numerics and Booleans
+CREATE TABLE types_showcase.all_numerics (
+ id SERIAL PRIMARY KEY,
+ col_smallint SMALLINT,
+ col_integer INTEGER,
+ col_bigint BIGINT,
+ col_numeric NUMERIC(18, 4),
+ col_decimal DECIMAL(10, 2),
+ col_real REAL,
+ col_double DOUBLE PRECISION,
+ col_money MONEY,
+ col_boolean BOOLEAN,
+ description TEXT
+);
+
+INSERT INTO types_showcase.all_numerics
+ (col_smallint, col_integer, col_bigint, col_numeric, col_decimal, col_real, col_double, col_money, col_boolean, description)
+VALUES
+ (32767, 2147483647, 9223372036854775807, 12345678901234.5678, 12345678.90, 3.14159, 2.718281828459045, '999.99'::MONEY, TRUE, 'Max boundary values'),
+ (-32768, -2147483648, -9223372036854775808, -12345678901234.5678, -12345678.90, -3.14159, -2.718281828459045, '-999.99'::MONEY, FALSE, 'Min boundary values'),
+ (0, 0, 0, 0.0000, 0.00, 0.0, 0.0, '0.00'::MONEY, TRUE, 'Zero values'),
+ (42, 1000, 1000000000, 42.4200, 99.50, 1.23, 4.56789, '1500.50'::MONEY, FALSE, 'Typical sample row'),
+ (NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'All NULLs');
+
+-- 3. Strings and Text
+CREATE TABLE types_showcase.all_strings (
+ id SERIAL PRIMARY KEY,
+ col_char CHAR(10),
+ col_varchar VARCHAR(100),
+ col_text TEXT,
+ col_name NAME,
+ description TEXT
+);
+
+INSERT INTO types_showcase.all_strings (col_char, col_varchar, col_text, col_name, description)
+VALUES
+ ('FIXED', 'Variable length string', 'Long multiline text paragraph with special characters: "quotes", \slashes\, and emoji 🚀 🐘 🔥', 'pg_identifier_name', 'Standard strings'),
+ ('UTF8', 'Привет, мир! こんにちは世界', 'Русский текст, иероглифы и диакритика: résumé, naïve, über', 'utf8_name', 'International Unicode strings'),
+ ('EMPTY', '', '', 'empty_test', 'Empty string test'),
+ ('SQL_DANGER', ''' OR ''1''=''1', 'SELECT * FROM users; DROP TABLE test; --', 'sql_inject', 'SQL escape check'),
+ (NULL, NULL, NULL, NULL, 'All NULL strings');
+
+-- 4. Date, Time and Temporal
+CREATE TABLE types_showcase.all_datetime (
+ id SERIAL PRIMARY KEY,
+ col_date DATE,
+ col_time TIME,
+ col_timetz TIMETZ,
+ col_timestamp TIMESTAMP,
+ col_timestamptz TIMESTAMPTZ,
+ col_interval INTERVAL,
+ description TEXT
+);
+
+INSERT INTO types_showcase.all_datetime (col_date, col_time, col_timetz, col_timestamp, col_timestamptz, col_interval, description)
+VALUES
+ ('2026-08-26', '10:30:00', '10:30:00+03:00', '2026-08-26 10:30:00', '2026-08-26 10:30:00+00:00', '1 year 2 months 3 days 4 hours 5 minutes 6 seconds', 'Present day full temporal'),
+ ('1970-01-01', '00:00:00', '00:00:00+00:00', '1970-01-01 00:00:00', '1970-01-01 00:00:00+00:00', '0 seconds', 'Unix Epoch'),
+ ('2099-12-31', '23:59:59', '23:59:59-08:00', '2099-12-31 23:59:59', '2099-12-31 23:59:59-08:00', '30 days', 'Future boundary'),
+ (NULL, NULL, NULL, NULL, NULL, NULL, 'All NULL timestamps');
+
+-- 5. JSON, JSONB, and XML
+CREATE TABLE types_showcase.all_json_xml (
+ id SERIAL PRIMARY KEY,
+ col_json JSON,
+ col_jsonb JSONB,
+ col_xml XML,
+ description TEXT
+);
+
+INSERT INTO types_showcase.all_json_xml (col_json, col_jsonb, col_xml, description)
+VALUES
+ (
+ '{"name": "Querya", "type": "Desktop Client", "version": "0.4.14", "open_source": true, "stats": {"stars": 1200, "contributors": 15}}',
+ '{"id": 42, "user": {"email": "alice@querya.dev", "roles": ["admin", "developer"]}, "settings": {"theme": "dark", "hz": 120, "telemetry": false}}',
+ XML '0.4.14Linux- DataGrid
- Sandbox
',
+ 'Rich nested JSON, JSONB and XML objects'
+ ),
+ (
+ '["apple", "banana", "cherry", {"nested": [1, 2, 3]}]',
+ '{"array": [10, 20, 30], "flags": {"a": true, "b": null}}',
+ XML '',
+ 'JSON Arrays and compact XML'
+ ),
+ (
+ '{}',
+ '[]',
+ XML '',
+ 'Empty JSON structures'
+ ),
+ (NULL, NULL, NULL, 'All NULL documents');
+
+-- 6. UUID, Identifiers and Network Addresses
+CREATE TABLE types_showcase.all_identifiers_and_network (
+ id SERIAL PRIMARY KEY,
+ col_uuid UUID,
+ col_inet INET,
+ col_cidr CIDR,
+ col_macaddr MACADDR,
+ col_macaddr8 MACADDR8,
+ col_oid OID,
+ description TEXT
+);
+
+INSERT INTO types_showcase.all_identifiers_and_network (col_uuid, col_inet, col_cidr, col_macaddr, col_macaddr8, col_oid, description)
+VALUES
+ ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', '192.168.1.100', '10.0.0.0/16', '08:00:2b:01:02:03', '08:00:2b:01:02:03:04:05', 16384, 'IPv4 & standard MAC'),
+ ('f47ac10b-58cc-4372-a567-0e02b2c3d479', '2001:0db8:85a3:0000:0000:8a2e:0370:7334', '2001:db8::/32', '00-50-56-C0-00-08', '00-50-56-FF-FE-C0-00-08', 32768, 'IPv6 & Extended MAC'),
+ (NULL, NULL, NULL, NULL, NULL, NULL, 'All NULL identifiers');
+
+-- 7. Binary and Bit Strings
+CREATE TABLE types_showcase.all_binary_and_bits (
+ id SERIAL PRIMARY KEY,
+ col_bytea BYTEA,
+ col_bit BIT(8),
+ col_varbit BIT VARYING(32),
+ description TEXT
+);
+
+INSERT INTO types_showcase.all_binary_and_bits (col_bytea, col_bit, col_varbit, description)
+VALUES
+ (E'\\xDEADBEEFCAFE0102030405', B'10101010', B'110010101111', 'Hex binary bytes and bitmasks'),
+ (E'Hello Querya Binary \\000 Test', B'11110000', B'1', 'ASCII text stored as BYTEA'),
+ (E'\\x', B'00000000', B'0', 'Zero / Empty bytes'),
+ (NULL, NULL, NULL, 'All NULL binary');
+
+-- 8. Arrays (1D and Multi-dimensional)
+CREATE TABLE types_showcase.all_arrays (
+ id SERIAL PRIMARY KEY,
+ col_int_array INT[],
+ col_text_array TEXT[],
+ col_uuid_array UUID[],
+ col_bool_array BOOLEAN[],
+ col_jsonb_array JSONB[],
+ col_matrix INT[][],
+ description TEXT
+);
+
+INSERT INTO types_showcase.all_arrays (col_int_array, col_text_array, col_uuid_array, col_bool_array, col_jsonb_array, col_matrix, description)
+VALUES
+ (
+ ARRAY[1, 2, 3, 42, 999],
+ ARRAY['Alpha', 'Beta', 'Gamma', 'Delta'],
+ ARRAY['a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::UUID, 'f47ac10b-58cc-4372-a567-0e02b2c3d479'::UUID],
+ ARRAY[TRUE, FALSE, TRUE, TRUE],
+ ARRAY['{"key": "a"}'::JSONB, '{"key": "b"}'::JSONB],
+ ARRAY[[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ 'Standard filled arrays and 2D matrix'
+ ),
+ (
+ '{}',
+ '{}',
+ '{}',
+ '{}',
+ '{}',
+ '{}',
+ 'Empty arrays'
+ ),
+ (NULL, NULL, NULL, NULL, NULL, NULL, 'All NULL arrays');
+
+-- 9. Ranges and Multiranges
+CREATE TABLE types_showcase.all_ranges (
+ id SERIAL PRIMARY KEY,
+ col_int4range INT4RANGE,
+ col_int8range INT8RANGE,
+ col_numrange NUMRANGE,
+ col_tsrange TSRANGE,
+ col_tstzrange TSTZRANGE,
+ col_daterange DATERANGE,
+ col_int4multirange INT4MULTIRANGE,
+ col_datemultirange DATEMULTIRANGE,
+ description TEXT
+);
+
+INSERT INTO types_showcase.all_ranges (col_int4range, col_int8range, col_numrange, col_tsrange, col_tstzrange, col_daterange, col_int4multirange, col_datemultirange, description)
+VALUES
+ (
+ '[1, 100)',
+ '[1000000, 9999999]',
+ '(10.5, 99.9)',
+ '[2026-01-01 00:00:00, 2026-12-31 23:59:59]',
+ '[2026-08-01 00:00:00+00, 2026-08-31 23:59:59+00)',
+ '[2026-01-01, 2026-06-30)',
+ '{[1, 10), [20, 30], [50, 60)}'::INT4MULTIRANGE,
+ '{[2026-01-01, 2026-01-31), [2026-03-01, 2026-03-31)}'::DATEMULTIRANGE,
+ 'Standard bounded ranges & multiranges'
+ ),
+ (
+ '(10,)',
+ '[, 1000]',
+ '(,)',
+ '(2026-01-01,)',
+ '(, 2026-12-31+00)',
+ 'empty',
+ '{}'::INT4MULTIRANGE,
+ '{}'::DATEMULTIRANGE,
+ 'Unbounded & empty ranges'
+ ),
+ (NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'All NULL ranges');
+
+-- 10. Geometric Types
+CREATE TABLE types_showcase.all_geometric (
+ id SERIAL PRIMARY KEY,
+ col_point POINT,
+ col_line LINE,
+ col_lseg LSEG,
+ col_box BOX,
+ col_path PATH,
+ col_polygon POLYGON,
+ col_circle CIRCLE,
+ description TEXT
+);
+
+INSERT INTO types_showcase.all_geometric (col_point, col_line, col_lseg, col_box, col_path, col_polygon, col_circle, description)
+VALUES
+ (
+ POINT(10.5, 20.3),
+ LINE '{1, -1, 0}',
+ LSEG '[(0,0), (10,10)]',
+ BOX '((10,10), (0,0))',
+ PATH '((0,0), (10,0), (10,10), (0,10))',
+ POLYGON '((0,0), (5,10), (10,0))',
+ CIRCLE '<(5,5), 10>',
+ '2D Geometric coordinates and shapes'
+ ),
+ (NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'All NULL geometric');
+
+-- 11. Custom Types (Enums, Domains, Composite)
+CREATE TABLE types_showcase.all_custom_entities (
+ id SERIAL PRIMARY KEY,
+ user_role types_showcase.user_role_enum NOT NULL DEFAULT 'viewer',
+ order_status types_showcase.order_status_enum NOT NULL DEFAULT 'pending',
+ age_positive types_showcase.positive_int,
+ contact_email types_showcase.valid_email,
+ location types_showcase.geo_coordinate,
+ home_address types_showcase.address_type,
+ notes TEXT
+);
+
+INSERT INTO types_showcase.all_custom_entities (user_role, order_status, age_positive, contact_email, location, home_address, notes)
+VALUES
+ ('admin', 'completed', 35, 'alex@querya.dev', ROW(52.5200, 13.4050, 34.0)::types_showcase.geo_coordinate, ROW('Unter den Linden 1', 'Berlin', '10117', 'DEU')::types_showcase.address_type, 'Admin user with composite fields'),
+ ('editor', 'processing', 28, 'elena@querya.dev', ROW(40.7128, -74.0060, 10.5)::types_showcase.geo_coordinate, ROW('5th Avenue 100', 'New York', '10001', 'USA')::types_showcase.address_type, 'Editor profile in NYC'),
+ ('guest', 'draft', 19, 'guest@example.com', NULL, NULL, 'Guest user without address');
diff --git a/docker/redis/seed.sh b/docker/redis/seed.sh
index 0066be9c..c6b4ded3 100755
--- a/docker/redis/seed.sh
+++ b/docker/redis/seed.sh
@@ -9,22 +9,36 @@ until redis-cli -h "$HOST" -p "$PORT" ping | grep -q PONG; do
sleep 1
done
-if redis-cli -h "$HOST" -p "$PORT" EXISTS querya:seed:marker | grep -q 1; then
- echo "Redis seed marker present — skipping."
- exit 0
-fi
-
-echo "Seeding Redis demo keys..."
-
-redis-cli -h "$HOST" -p "$PORT" <<'EOF'
-SET querya:demo:string "Hello from Querya Docker stack"
-SET querya:config:version "1"
-HSET querya:user:1 name "Alice Martin" email "alice@example.com" city "Berlin"
-HSET querya:user:2 name "Bob Smith" email "bob@example.com" city "London"
-RPUSH querya:tasks:open "Review PR" "Write docs" "Test Redis key editor"
-SADD querya:tags:popular redis docker mongodb postgresql mysql
-ZADD querya:leaderboard 980 "player_alpha" 875 "player_beta" 640 "player_gamma"
+echo "Flushing and re-seeding Redis keys..."
+redis-cli -h "$HOST" -p "$PORT" FLUSHALL
+
+redis-cli -h "$HOST" -p "$PORT" <<'REDIS_EOF'
+SET querya:app:name "Querya Desktop"
+SET querya:app:version "0.4.14"
+SET querya:config:theme "dark"
+SET querya:config:scale "1.0"
+SET querya:config:refresh_rate_hz "120"
+SET querya:metrics:uptime_seconds "86400"
+
+HSET querya:user:1 id "1" name "Alice Martin" email "alice@example.com" city "Berlin" role "admin" is_active "true"
+HSET querya:user:2 id "2" name "Bob Smith" email "bob@example.com" city "London" role "editor" is_active "true"
+HSET querya:user:3 id "3" name "Carla Ruiz" email "carla@example.com" city "Madrid" role "viewer" is_active "false"
+HSET querya:user:4 id "4" name "Daisuke Sato" email "daisuke@example.com" city "Tokyo" role "customer" is_active "true"
+
+RPUSH querya:queue:tasks "Task 1: Generate monthly analytics" "Task 2: Sync marketplace plugins" "Task 3: Run CI regression tests" "Task 4: Clear expired sessions"
+RPUSH querya:logs:recent "[INFO] 2026-08-26 10:00:00 - Server started" "[INFO] 2026-08-26 10:05:00 - Connection pool initialized" "[WARN] 2026-08-26 10:15:00 - High memory watermark reached"
+
+SADD querya:tags:all "postgresql" "mysql" "sqlite" "redis" "mongodb" "clickhouse" "rust" "flutter" "datagrid" "sdui"
+SADD querya:features:enabled "virtual_grid" "in_place_editing" "ast_filter" "quick_calc" "fluid_sidebar" "sandbox"
+
+ZADD querya:leaderboard:points 15200 "alice_martin" 12400 "bob_smith" 9800 "carla_ruiz" 7500 "daisuke_sato" 4200 "elena_popova"
+ZADD querya:metrics:cpu_usage 12.5 "host_01" 34.8 "host_02" 78.2 "host_03" 5.1 "host_04"
+
+XADD querya:stream:events * event_type "user_login" user_id "1" ip "192.168.1.42"
+XADD querya:stream:events * event_type "query_executed" db "postgresql" duration_ms "12"
+XADD querya:stream:events * event_type "export_csv" rows "5000" duration_ms "45"
+
SET querya:seed:marker "1"
-EOF
+REDIS_EOF
echo "Redis seed complete."
diff --git a/docker/sqlite/init.sql b/docker/sqlite/init.sql
index 172eea4c..ca5de5fa 100644
--- a/docker/sqlite/init.sql
+++ b/docker/sqlite/init.sql
@@ -1,41 +1,140 @@
--- Querya SQLite Test Database Initialization
--- This script runs once via Docker to seed the local querya.db file.
+-- Querya SQLite Comprehensive Test Database Initialization
+-- This script runs via Docker / init scripts to seed querya.db.
-CREATE TABLE IF NOT EXISTS users (
+DROP TABLE IF EXISTS order_lines;
+DROP TABLE IF EXISTS orders;
+DROP TABLE IF EXISTS products;
+DROP TABLE IF EXISTS users;
+DROP TABLE IF EXISTS all_sqlite_types;
+DROP VIEW IF EXISTS customer_spending;
+
+CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
- username TEXT NOT NULL,
+ username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL UNIQUE,
+ full_name TEXT NOT NULL,
+ city TEXT DEFAULT 'Berlin',
+ is_active INTEGER DEFAULT 1,
+ metadata TEXT DEFAULT '{}',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-CREATE TABLE IF NOT EXISTS products (
+CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
+ sku TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
+ category TEXT DEFAULT 'Peripherals',
price REAL NOT NULL,
- stock INTEGER DEFAULT 0
+ stock INTEGER DEFAULT 0,
+ is_available INTEGER DEFAULT 1,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-CREATE TABLE IF NOT EXISTS orders (
+CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
total REAL NOT NULL,
+ discount REAL DEFAULT 0.0,
status TEXT DEFAULT 'pending',
+ shipping_address TEXT DEFAULT '{}',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id)
);
--- Seed Data
-INSERT OR IGNORE INTO users (id, username, email) VALUES
-(1, 'alice_smith', 'alice@example.com'),
-(2, 'bob_jones', 'bob@example.com'),
-(3, 'charlie_brown', 'charlie@example.com');
-
-INSERT OR IGNORE INTO products (id, name, price, stock) VALUES
-(1, 'Laptop Pro', 1299.99, 50),
-(2, 'Wireless Mouse', 49.99, 200),
-(3, 'Mechanical Keyboard', 149.50, 75);
-
-INSERT OR IGNORE INTO orders (id, user_id, total, status) VALUES
-(1, 1, 1299.99, 'completed'),
-(2, 2, 49.99, 'shipped'),
-(3, 1, 149.50, 'pending');
+CREATE TABLE all_sqlite_types (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ col_integer INTEGER,
+ col_real REAL,
+ col_text TEXT,
+ col_blob BLOB,
+ col_numeric NUMERIC,
+ col_boolean INTEGER,
+ col_datetime DATETIME,
+ col_json TEXT,
+ description TEXT
+);
+
+-- Seed all_sqlite_types
+INSERT INTO all_sqlite_types (col_integer, col_real, col_text, col_blob, col_numeric, col_boolean, col_datetime, col_json, description)
+VALUES
+(
+ 9223372036854775807, 3.141592653589793, 'Unicode text with emoji 🚀 🗄️',
+ X'DEADBEEFCAFE0102030405', 12345678.90, 1,
+ '2026-08-26 10:30:00',
+ '{"app": "Querya", "engine": "SQLite FFI", "features": ["VirtualGrid", "ASTFilter", "QuickCalc"]}',
+ 'Max boundary and rich row'
+),
+(
+ -9223372036854775808, -2.71828, 'Negative boundaries',
+ X'00000000', -12345678.90, 0,
+ '1970-01-01 00:00:00',
+ '["item1", "item2", 42]',
+ 'Min boundary row'
+),
+(
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ 'All NULL row'
+);
+
+-- Seed 50 users
+WITH RECURSIVE cnt(x) AS (
+ SELECT 1
+ UNION ALL
+ SELECT x+1 FROM cnt WHERE x < 50
+)
+INSERT INTO users (username, email, full_name, city, is_active, metadata, created_at)
+SELECT
+ 'user_' || x,
+ 'user' || x || '@example.com',
+ 'User Name ' || x,
+ CASE (x % 5) WHEN 0 THEN 'Berlin' WHEN 1 THEN 'London' WHEN 2 THEN 'Paris' WHEN 3 THEN 'Tokyo' ELSE 'New York' END,
+ CASE WHEN (x % 5 = 0) THEN 0 ELSE 1 END,
+ '{"tier": "' || (CASE WHEN x % 3 = 0 THEN 'gold' ELSE 'standard' END) || '", "points": ' || (x * 100) || '}',
+ DATETIME('now', '-' || x || ' days')
+FROM cnt;
+
+-- Seed 30 products
+WITH RECURSIVE cnt(x) AS (
+ SELECT 1
+ UNION ALL
+ SELECT x+1 FROM cnt WHERE x < 30
+)
+INSERT INTO products (sku, name, category, price, stock, is_available, created_at)
+SELECT
+ 'SKU-' || PRINTF('%04d', x),
+ 'Product ' || x,
+ CASE (x % 4) WHEN 0 THEN 'Hardware' WHEN 1 THEN 'Peripherals' WHEN 2 THEN 'Audio' ELSE 'Furniture' END,
+ ROUND(19.99 + (x * 5.75), 2),
+ (x * 10) % 150,
+ 1,
+ DATETIME('now', '-' || x || ' days')
+FROM cnt;
+
+-- Seed 150 orders
+WITH RECURSIVE cnt(x) AS (
+ SELECT 1
+ UNION ALL
+ SELECT x+1 FROM cnt WHERE x < 150
+)
+INSERT INTO orders (user_id, total, discount, status, shipping_address, created_at)
+SELECT
+ 1 + (x % 50),
+ ROUND(29.99 + (x * 4.25), 2),
+ ROUND((x % 10) * 1.5, 2),
+ CASE (x % 5) WHEN 0 THEN 'completed' WHEN 1 THEN 'shipped' WHEN 2 THEN 'processing' WHEN 3 THEN 'cancelled' ELSE 'pending' END,
+ '{"street": "' || x || ' High St", "zip": "100' || (x % 90) || '"}',
+ DATETIME('now', '-' || x || ' hours')
+FROM cnt;
+
+CREATE VIEW customer_spending AS
+SELECT
+ u.id AS user_id,
+ u.username,
+ u.email,
+ u.city,
+ COUNT(o.id) AS order_count,
+ COALESCE(SUM(o.total), 0.0) AS total_spent,
+ MAX(o.created_at) AS last_order_date
+FROM users u
+LEFT JOIN orders o ON o.user_id = u.id
+GROUP BY u.id, u.username, u.email, u.city;
diff --git a/docs/tz-block-d-external-plugin.md b/docs/tz-block-d-external-plugin.md
index 0412ba5c..4dc09195 100644
--- a/docs/tz-block-d-external-plugin.md
+++ b/docs/tz-block-d-external-plugin.md
@@ -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
+ }
+ ```
+
diff --git a/lib/app/app.dart b/lib/app/app.dart
index 51f10be5..2e6b16c2 100644
--- a/lib/app/app.dart
+++ b/lib/app/app.dart
@@ -60,6 +60,12 @@ class QueryaApp extends StatelessWidget {
themeMode: themeController.themeMode,
materialTheme: themeController.materialThemeFor(colorScheme),
debugShowCheckedModeBanner: false,
+ menuHandler: const PopoverOverlayHandler(
+ defaultShowDuration: Duration(milliseconds: 60),
+ defaultDismissDuration: Duration(milliseconds: 50),
+ showCurve: Curves.easeOutCubic,
+ dismissCurve: Curves.easeIn,
+ ),
enableThemeAnimation: themeAnimEnabled,
themeAnimationDuration: themeDuration,
themeAnimationCurve: themeCurve,
diff --git a/lib/core/database/compact_result_dataset.dart b/lib/core/database/compact_result_dataset.dart
new file mode 100644
index 00000000..ce085630
--- /dev/null
+++ b/lib/core/database/compact_result_dataset.dart
@@ -0,0 +1,403 @@
+import 'dart:collection';
+import 'dart:typed_data';
+
+import 'result_row_string_convert.dart';
+
+/// Supported compact column storage kinds.
+enum CompactColumnKind {
+ int64,
+ float64,
+ string,
+ generic,
+}
+
+/// Columnar storage interface for a single database column in a result set.
+sealed class CompactColumn {
+ int get length;
+ CompactColumnKind get kind;
+ Object? rawValueAt(int index);
+ String stringValueAt(int index, [StringInternPool? pool]);
+ bool isNull(int index);
+}
+
+/// Compact 64-bit integer column backed by [Int64List] and a packed null bitmap.
+class Int64CompactColumn implements CompactColumn {
+ Int64CompactColumn({
+ required this.values,
+ required this.nullBitmap,
+ required this.length,
+ });
+
+ final Int64List values;
+ final Uint8List nullBitmap; // 1 bit per row indicates null
+ @override
+ final int length;
+
+ @override
+ CompactColumnKind get kind => CompactColumnKind.int64;
+
+ @override
+ bool isNull(int index) {
+ final byteIndex = index >> 3;
+ final bitOffset = index & 7;
+ return (nullBitmap[byteIndex] & (1 << bitOffset)) != 0;
+ }
+
+ @override
+ Object? rawValueAt(int index) {
+ if (isNull(index)) return null;
+ return values[index];
+ }
+
+ @override
+ String stringValueAt(int index, [StringInternPool? pool]) {
+ if (isNull(index)) return 'NULL';
+ final val = values[index];
+ if (val >= 0 && val <= 10 && pool != null) {
+ return pool.internObject(val);
+ }
+ return val.toString();
+ }
+}
+
+/// Compact 64-bit float column backed by [Float64List] and a packed null bitmap.
+class Float64CompactColumn implements CompactColumn {
+ Float64CompactColumn({
+ required this.values,
+ required this.nullBitmap,
+ required this.length,
+ });
+
+ final Float64List values;
+ final Uint8List nullBitmap;
+ @override
+ final int length;
+
+ @override
+ CompactColumnKind get kind => CompactColumnKind.float64;
+
+ @override
+ bool isNull(int index) {
+ final byteIndex = index >> 3;
+ final bitOffset = index & 7;
+ return (nullBitmap[byteIndex] & (1 << bitOffset)) != 0;
+ }
+
+ @override
+ Object? rawValueAt(int index) {
+ if (isNull(index)) return null;
+ return values[index];
+ }
+
+ @override
+ String stringValueAt(int index, [StringInternPool? pool]) {
+ if (isNull(index)) return 'NULL';
+ final val = values[index];
+ if (val == val.toInt() && !val.isNaN && !val.isInfinite) {
+ return val.toInt().toString();
+ }
+ return val.toString();
+ }
+}
+
+/// String column backed by interned String references.
+class StringCompactColumn implements CompactColumn {
+ StringCompactColumn({
+ required this.values,
+ required this.length,
+ });
+
+ final List values;
+ @override
+ final int length;
+
+ @override
+ CompactColumnKind get kind => CompactColumnKind.string;
+
+ @override
+ bool isNull(int index) => values[index] == null;
+
+ @override
+ Object? rawValueAt(int index) => values[index];
+
+ @override
+ String stringValueAt(int index, [StringInternPool? pool]) {
+ final val = values[index];
+ if (val == null) return 'NULL';
+ return pool != null ? pool.intern(val) : val;
+ }
+}
+
+/// Generic object column for composite or unparsed database types.
+class GenericCompactColumn implements CompactColumn {
+ GenericCompactColumn({
+ required this.values,
+ required this.length,
+ });
+
+ final List