SQL Reference
CSharpDB implements its own documented SQL dialect with a tokenizer, parser, query planner, and expression evaluator.
Data Definition (DDL)
CREATE TABLE
CREATE TABLE Employees (
Id INTEGER PRIMARY KEY,
Name TEXT NOT NULL,
DeptId INTEGER,
Salary REAL,
HireDate TEXT,
Photo BLOB
);
Temporary Tables
CREATE TEMP TABLE StageEmployees (
Id INTEGER PRIMARY KEY IDENTITY,
Name TEXT
);
INSERT INTO StageEmployees (Name) VALUES ('Ada');
PERSIST TEMP TABLE StageEmployees AS ImportedEmployees;
DROP TEMP TABLE IF EXISTS StageEmployees;
Temporary tables are in-memory and session-scoped. They resolve before durable tables, are hidden from sys.tables and backups, and appear only in sys.temp_tables/sys_temp_tables and sys.temp_columns/sys_temp_columns. Stateless HTTP/gRPC SQL execution rejects temp commands; use transaction sessions for remote temp workflows.
CREATE INDEX
CREATE INDEX idx_emp_dept ON Employees (DeptId);
CREATE UNIQUE INDEX idx_emp_name ON Employees (Name);
CREATE VIEW
CREATE VIEW HighEarners AS
SELECT Name, Salary FROM Employees WHERE Salary > 100000;
CREATE TRIGGER
CREATE TRIGGER log_insert
AFTER INSERT ON Employees
BEGIN
INSERT INTO AuditLog VALUES ('INSERT', NEW.Name);
END;
ALTER TABLE & DROP
ALTER TABLE Employees ADD COLUMN Title TEXT;
ALTER TABLE Employees DROP CONSTRAINT fk_employees_deptid_a1b2c3d4;
ALTER TABLE Employees RENAME INDEX idx_emp_dept TO idx_employees_department;
DROP VIEW HighEarners;
DROP INDEX idx_employees_department;
DROP TABLE Employees;
Data Manipulation (DML)
EXPLAIN and profile output
EXPLAIN SELECT Name FROM Employees WHERE Id = 1;
EXPLAIN ANALYZE SELECT Name FROM Employees WHERE Id = 1;
EXPLAIN ANALYZE UPDATE Employees SET Salary = Salary + 1000 WHERE Id = 1;
EXPLAIN returns the selected physical operator tree without opening it. EXPLAIN ANALYZE executes exactly once and reports actual rows, loops, and elapsed time; DML profile statements therefore mutate under normal transaction semantics. Estimated costs use stable relative row-work units for comparing choices, not elapsed time; row estimates remain nullable when cardinality is unavailable. Output is a bounded structural rowset available through direct, ADO.NET, HTTP, and gRPC clients; its 256 KiB limit is an inline content budget rather than an exact serialized transport size. Predicate values are redacted. Cancellation and execution errors remain failures, with a bounded, redacted partial-profile summary attached to failure diagnostics where safe. See the full contract and current non-executing planning restrictions.
INSERT
INSERT INTO Employees (Id, Name, DeptId, Salary)
VALUES (1, 'Alice', 10, 95000.0);
INSERT INTO Employees VALUES
(2, 'Bob', 20, 88000.0, '2024-01-15', NULL);
SELECT
-- Basic query with filtering and ordering
SELECT Name, Salary FROM Employees
WHERE Salary > 80000 ORDER BY Salary DESC;
-- DISTINCT, LIMIT, OFFSET
SELECT DISTINCT DeptId FROM Employees LIMIT 10 OFFSET 5;
-- JOINs
SELECT e.Name, d.DeptName
FROM Employees e
INNER JOIN Departments d ON e.DeptId = d.Id;
SELECT e.Name, d.DeptName
FROM Employees e
LEFT JOIN Departments d ON e.DeptId = d.Id;
-- Aggregates and GROUP BY
SELECT DeptId, COUNT(*), AVG(Salary), MAX(Salary)
FROM Employees
GROUP BY DeptId
HAVING COUNT(*) > 3;
-- CTEs (Common Table Expressions)
WITH TopEarners AS (
SELECT Name, Salary FROM Employees
WHERE Salary > 100000
)
SELECT * FROM TopEarners ORDER BY Salary DESC;
-- Subqueries
SELECT Name FROM Employees
WHERE DeptId IN (SELECT Id FROM Departments WHERE Region = 'West');
SELECT Name FROM Employees e
WHERE EXISTS (SELECT 1 FROM Reviews r WHERE r.EmpId = e.Id);
-- Set operations
SELECT Name FROM Employees WHERE DeptId = 10
UNION
SELECT Name FROM Contractors WHERE Active = 1;
UPDATE & DELETE
UPDATE Employees SET Salary = Salary * 1.1 WHERE DeptId = 10;
DELETE FROM Employees WHERE Id = 99;
Data Hygiene
Use SQL-first hygiene commands to preview duplicates, deduplicate or merge rows transactionally, store audit-only validation rules, and find orphaned child values.
-- Preview duplicate groups without changing data
FIND DUPLICATES IN Customers ON Email COLLATE NOCASE;
-- Fill NULL winner fields when exactly one duplicate value is available
MERGE DUPLICATES Customers ON Email;
-- Delete non-winners through normal index/FK/trigger mutation paths
DEDUP Customers ON Email KEEP FIRST;
-- Store and evaluate audit-only SQL validation rules
CREATE VALIDATION RULE ValidEmail
ON Customers.Email
AS Email LIKE '%@%'
MESSAGE 'Email must contain @';
VALIDATE TABLE Customers;
-- Check declared FKs or an explicit child/parent relationship
FIND ORPHANS IN Bookings;
FIND ORPHANS IN Bookings.BookId REFERENCES Books.Id;
Hygiene commands return normal query rows, so they work through embedded SQL, ADO.NET, Admin query tabs, the CLI, HTTP, and gRPC. Validation rules are stored in a hidden internal table and exposed through sys.validation_rules and sys_validation_rules.
Built-in Functions
String Functions
| Function | Description |
|---|---|
UPPER(text) | Convert to uppercase |
LOWER(text) | Convert to lowercase |
LENGTH(text) | String length |
SUBSTR(text, start, len) | Substring extraction |
TRIM(text) | Remove leading/trailing whitespace |
REPLACE(text, from, to) | String replacement |
INSTR(text, search) | Find position of substring |
ORDINAL_STARTS_WITH(text, prefix) | Case-sensitive ordinal prefix test; returns 1, 0, or NULL |
ORDINAL_ENDS_WITH(text, suffix) | Case-sensitive ordinal suffix test; returns 1, 0, or NULL |
ORDINAL_CONTAINS(text, search) | Case-sensitive ordinal substring test; returns 1, 0, or NULL |
The ordinal search functions use .NET ordinal UTF-16 code-unit semantics and do not apply SQL collation, wildcard, or escape rules. In particular, %, _, and backslash are ordinary characters.
XML Functions (XPath 1.0)
| Function | Description |
|---|---|
XML_EXISTS(xml, xpath [, namespace_json]) | Evaluate the XPath effective boolean value; returns 1, 0, or NULL. XMLEXISTS is an alias. |
XML_VALUE(xml, xpath [, namespace_json]) | Return one XPath value as text, or NULL when a node-set is empty. |
Both functions accept an XML or text document and an XPath 1.0 expression. The optional third argument is a JSON object that maps XPath prefixes to namespace URIs, for example XML_VALUE(payload, '/o:order/@id', '{"o":"urn:orders"}'). If any supplied argument is NULL, the result is NULL.
For a node-set, XML_VALUE returns the XPath string value of its single node, returns NULL for no nodes, and reports a type mismatch when more than one node matches. XPath scalar results are converted using XPath string semantics; select one node explicitly, such as (/root/item)[1], when multiple matches are possible.
XML parsing is bounded and secure: DTD declarations and external entities are prohibited. This release supports the function-style APIs shown above; standard SQL XMLEXISTS(... PASSING ...) syntax, XML_TABLE, and XML path indexes are not yet supported.
Math Functions
| Function | Description |
|---|---|
ABS(value) | Absolute value |
ROUND(value, digits) | Round to N decimal places |
SGN(value) | Sign of a number (-1, 0, 1) |
INT(value) / FLOOR(value) | Floor a numeric value |
FIX(value) | Truncate a numeric value toward zero |
Conditional & Type Functions
| Function | Description |
|---|---|
TEXT(value) | Convert a value to CSharpDB display text |
NZ(value [, fallback]) | Replace NULL/empty input using Access-style semantics |
COALESCE(a, b, ...) | First non-null value |
NULLIF(a, b) | NULL if a equals b |
IFNULL(a, b) | b if a is null |
IIF(cond, then, else) | Conditional expression |
Aggregate Functions
| Function | Description |
|---|---|
COUNT(*) / COUNT(col) | Count rows or non-null values |
SUM(col) | Sum of values |
AVG(col) | Average of values |
MIN(col) | Minimum value |
MAX(col) | Maximum value |
System Catalog
Query database metadata through built-in system views:
| View | Description |
|---|---|
sys.tables | All tables and their root page numbers |
sys.columns | Column definitions for all tables |
sys.indexes | Index definitions and backing B+tree info |
sys.check_constraints | Named and unnamed CHECK expressions and original column/table scope |
sys.key_constraints | Ordered logical primary/unique key columns and backing-index ownership |
sys.foreign_keys | Ordered child/parent column pairs, delete actions, and support-index ownership for scalar and composite foreign keys |
sys.functions | Built-in aliases/signatures/semantics plus registered scalar callback metadata |
sys.views | View definitions and SQL |
sys.triggers | Trigger metadata |
sys.validation_rules | Database-owned audit validation rules and their SQL expressions |
sys.temp_tables | Current-session temporary table metadata |
sys.temp_columns | Current-session temporary column metadata |
sys.table_stats | Row count and page usage per table |
sys.column_stats | Cardinality, NULL count, min/max per column |
Stored Procedures
CSharpDB supports stored procedures with typed parameters, persisted in the database catalog.
// Create a procedure
await client.CreateProcedureAsync(new ProcedureDefinition
{
Name = "GetEmployeesByDept",
BodySql = "SELECT * FROM Employees WHERE DeptId = @deptId",
Parameters = [
new ProcedureParameterDefinition { Name = "deptId", Type = DbType.Integer }
]
});
// Execute
var result = await client.ExecuteProcedureAsync(
"GetEmployeesByDept",
new Dictionary<string, object?> { ["deptId"] = 10 });