-
Notifications
You must be signed in to change notification settings - Fork 0
Database Server
DatabaseServer is a complete database management system implementation built on ConnectionServer. It provides query execution, pluggable storage engines, and MySQL-compatible query parsing.
┌──────────────────────────────────────────────────────┐
│ DatabaseServer (Query coordination) │ ← YOU ARE HERE
│ ├─ DatabaseEngine (Query execution) │
│ └─ StorageEngine (Persistence) │
│ ├─ JsonStorageEngine │
│ ├─ XmlStorageEngine │
│ └─ Custom implementations │
├──────────────────────────────────────────────────────┤
│ ConnectionServer (TCP transport) │
├──────────────────────────────────────────────────────┤
│ Server (abstract base) │
└──────────────────────────────────────────────────────┘
Key Files:
-
connection/database/DatabaseServer.java- Server orchestration -
connection/database/DatabaseEngine.java- Query execution -
connection/database/StorageEngine.java- Persistence interface -
connection/database/json/JsonStorageEngine.java- JSON persistence -
connection/database/xml/XmlStorageEngine.java- XML persistence -
connection/database/mysql/MySqlServer.java- MySQL implementation
import jsi.connection.database.*;
import jsi.connection.database.json.JsonStorageEngine;
import jsi.connection.database.mysql.MySqlServer;
public class MyDatabaseServer {
public static void main(String[] args) {
// 1. Create storage engine
StorageEngine storage = new JsonStorageEngine("./database");
// 2. Create database engine
DatabaseEngine engine = new DatabaseEngine(storage);
// 3. Create and start server
DatabaseServer server = new MySqlServer(3306, engine);
System.out.println("Database server started on port 3306");
server.start();
}
}This creates a MySQL-compatible database server with JSON file persistence.
The database system is split into three independent components:
public abstract class DatabaseServer extends ConnectionServer {
private DatabaseEngine databaseEngine;
public DatabaseServer(int port, DatabaseEngine databaseEngine) {
super(port);
this.databaseEngine = databaseEngine;
}
@Override
public QueryResult handleRequest(Request query) {
if (query instanceof Query q) {
return databaseEngine.execute(q);
}
throw new IllegalArgumentException("Invalid request type");
}
}Responsibilities:
- Accept TCP connections (inherited from
ConnectionServer) - Parse incoming requests into
Queryobjects - Delegate execution to
DatabaseEngine - Return
QueryResultto client
Key insight: DatabaseServer knows nothing about query execution or storage - it's pure orchestration.
public abstract class DatabaseEngine {
private StorageEngine storageEngine;
public DatabaseEngine(StorageEngine storageEngine) {
this.storageEngine = storageEngine;
}
public QueryResult execute(Query query) {
OperationType type = query.getQueryType().getOperationType();
String collection = query.getTargetCollection();
QueryCondition condition = query.getCondition();
try {
if (type == OperationType.READ) {
List<List<Field>> results = storageEngine.read(collection, condition);
return new QueryResult(true, "Read successful", results);
}
else if (type == OperationType.CREATE || type == OperationType.UPDATE) {
storageEngine.write(collection, query.getAffectedFields());
return new QueryResult(true, "Write successful", null);
}
else if (type == OperationType.DELETE) {
storageEngine.delete(collection, condition);
return new QueryResult(true, "Delete successful", null);
}
return new QueryResult(false, "Unsupported operation", null);
} catch (Exception e) {
return new QueryResult(false, "Operation failed: " + e.getMessage(), null);
}
}
public abstract void beginTransaction();
public abstract void commit();
public abstract void rollback();
}Responsibilities:
- Map query types (SELECT, INSERT, etc.) to storage operations
- Transaction management (abstract - subclass responsibility)
- Error handling and result formatting
Key insight: DatabaseEngine knows nothing about how data is stored - it delegates to StorageEngine.
public abstract class StorageEngine {
protected String storagePath;
public StorageEngine(String storagePath) {
this.storagePath = storagePath;
}
// Core CRUD operations
public abstract void write(String collection, List<Field> data)
throws IOException;
public abstract List<List<Field>> read(String collection, QueryCondition condition)
throws IOException;
public abstract void delete(String collection, QueryCondition condition)
throws IOException;
// Format conversion (implementation-specific)
protected abstract String fieldToString(Field field);
protected abstract String recordToString(List<Field> fields);
protected abstract String recordsToArray(List<List<Field>> records);
protected abstract List<List<Field>> arrayToRecords(String array);
}Responsibilities:
- Read/write data from/to persistent storage
- Format conversion (objects ↔ storage format)
- Query condition evaluation (filtering)
Key insight: StorageEngine knows nothing about query syntax or database semantics - it's pure storage.
File: connection/database/Field.java
public class Field {
private String name; // Column name
private Object value; // Column value
public Field(String name, Object value) {
this.name = name;
this.value = value;
}
public String getName() { return name; }
public Object getValue() { return value; }
}Example:
Field idField = new Field("id", 123);
Field nameField = new Field("name", "Alice");
Field emailField = new Field("email", "[email protected]");A record is a list of Field objects representing one row:
List<Field> record = Arrays.asList(
new Field("id", 1),
new Field("name", "Alice"),
new Field("email", "[email protected]")
);A collection (table) is a list of records:
List<List<Field>> users = Arrays.asList(
Arrays.asList(
new Field("id", 1),
new Field("name", "Alice"),
new Field("email", "[email protected]")
),
Arrays.asList(
new Field("id", 2),
new Field("name", "Bob"),
new Field("email", "[email protected]")
)
);File: connection/database/query/Query.java
public abstract class Query implements Request {
private String rawQuery;
public Query(String rawQuery) { this.rawQuery = rawQuery; }
// Query components (parsed from rawQuery)
public abstract QueryType getQueryType(); // SELECT, INSERT, UPDATE, DELETE
public abstract String getTargetCollection(); // Table name
public abstract List<Field> getAffectedFields(); // Fields to insert/update
public abstract QueryCondition getCondition(); // WHERE clause
@Override
public String serialize() { return rawQuery; }
}File: connection/database/query/QueryType.java
public interface QueryType {
enum OperationType {
CREATE, // INSERT
READ, // SELECT
UPDATE, // UPDATE
DELETE // DELETE
}
OperationType getOperationType();
}File: connection/database/query/QueryCondition.java
public class QueryCondition {
// Simple condition: field = value
private String fieldName;
private Object value;
// Complex condition: (cond1 AND cond2) OR cond3
private LogicalOperator logicalOperator; // AND, OR
private List<QueryCondition> subConditions;
public boolean isSimpleCondition() {
return fieldName != null && value != null;
}
public boolean isComplexCondition() {
return logicalOperator != null && subConditions != null;
}
public enum LogicalOperator { AND, OR }
}Examples:
// Simple: WHERE name = 'Alice'
QueryCondition simple = new QueryCondition("name", "Alice");
// Complex: WHERE age > 18 AND city = 'NYC'
QueryCondition complex = new QueryCondition(
QueryCondition.LogicalOperator.AND,
Arrays.asList(
new QueryCondition("age", 18),
new QueryCondition("city", "NYC")
)
);File: connection/database/query/QueryResult.java
public class QueryResult implements Response {
private boolean success;
private String message;
private List<List<Field>> data; // Result rows (null for non-SELECT queries)
public QueryResult(boolean success, String message, List<List<Field>> data) {
this.success = success;
this.message = message;
this.data = data;
}
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public List<List<Field>> getData() { return data; }
@Override
public String serialize() {
// Convert to string format for transmission
}
}File: connection/database/json/JsonStorageEngine.java
Stores data in JSON files, one file per collection.
File format example (database/table_users.json):
[
{"id": "1", "name": "Alice", "email": "[email protected]"},
{"id": "2", "name": "Bob", "email": "[email protected]"},
{"id": "3", "name": "Charlie", "email": "[email protected]"}
]Key methods:
public class JsonStorageEngine extends StorageEngine {
@Override
protected String fieldToString(Field field) {
// Converts: Field("name", "Alice") → "name":"Alice"
// Handles escaping: " → \", \ → \\, \n → \\n
}
@Override
protected String recordToString(List<Field> fields) {
// Converts: [Field("id", 1), Field("name", "Alice")]
// → {"id": 1, "name": "Alice"}
}
@Override
protected String recordsToArray(List<List<Field>> records) {
// Converts: List of records → JSON array string
}
@Override
protected List<List<Field>> arrayToRecords(String jsonArray) {
// Parses JSON array string → List of Field lists
// Custom parser (no external JSON library)
}
}Usage:
StorageEngine storage = new JsonStorageEngine("./database");
// Write operation creates/updates file
storage.write("users", Arrays.asList(
new Field("id", "1"),
new Field("name", "Alice")
));
// Read operation loads from file
List<List<Field>> results = storage.read("users", null);File: connection/database/xml/XmlStorageEngine.java
Stores data in XML files, one file per collection.
File format example (database/table_users.xml):
<?xml version="1.0" encoding="UTF-8"?>
<collection>
<record>
<id>1</id>
<name>Alice</name>
<email>[email protected]</email>
</record>
<record>
<id>2</id>
<name>Bob</name>
<email>[email protected]</email>
</record>
</collection>Key methods:
public class XmlStorageEngine extends StorageEngine {
@Override
protected String fieldToString(Field field) {
// Converts: Field("name", "Alice") → <name>Alice</name>
// Handles escaping: < → <, > → >, & → &
}
@Override
protected String recordToString(List<Field> fields) {
// Converts: List of Fields → <record>...</record>
}
@Override
protected String recordsToArray(List<List<Field>> records) {
// Converts: List of records → XML document
}
@Override
protected List<List<Field>> arrayToRecords(String xmlArray) {
// Parses XML document → List of Field lists
// Custom parser (no external XML library)
}
}Usage:
StorageEngine storage = new XmlStorageEngine("./database");
// Write operation creates/updates XML file
storage.write("users", Arrays.asList(
new Field("id", "1"),
new Field("name", "Alice")
));
// Read operation parses XML file
List<List<Field>> results = storage.read("users", null);Create your own storage backend:
public class PostgresStorageEngine extends StorageEngine {
private Connection dbConnection;
public PostgresStorageEngine(String jdbcUrl) {
super(jdbcUrl);
// Initialize JDBC connection
}
@Override
public void write(String collection, List<Field> data) throws IOException {
// INSERT INTO collection (field1, field2) VALUES (?, ?)
}
@Override
public List<List<Field>> read(String collection, QueryCondition condition) {
// SELECT * FROM collection WHERE ...
}
@Override
public void delete(String collection, QueryCondition condition) {
// DELETE FROM collection WHERE ...
}
// Format methods not needed for SQL backends
@Override
protected String fieldToString(Field field) { return null; }
@Override
protected String recordToString(List<Field> fields) { return null; }
@Override
protected String recordsToArray(List<List<Field>> records) { return null; }
@Override
protected List<List<Field>> arrayToRecords(String array) { return null; }
}File: connection/database/mysql/MySqlServer.java
public class MySqlServer extends DatabaseServer {
public MySqlServer(int port, DatabaseEngine databaseEngine) {
super(port, databaseEngine);
}
@Override
protected Request parseRequest(String input) {
// Parse MySQL query syntax
// TODO: Implementation in progress
throw new UnsupportedOperationException("Unimplemented method 'parseRequest'");
}
}Current status: Partial implementation. Query parsing in development.
File: connection/database/mysql/query/MySqlQuery.java
Parses MySQL-specific SQL syntax:
public class MySqlQuery extends Query {
public MySqlQuery(String rawQuery) {
super(rawQuery);
parseQuery(rawQuery);
}
private void parseQuery(String sql) {
// Extract query type (SELECT, INSERT, UPDATE, DELETE)
// Extract table name
// Extract fields and conditions
// Uses regex-based parsing
}
@Override
public QueryType getQueryType() { /* ... */ }
@Override
public String getTargetCollection() { /* ... */ }
@Override
public List<Field> getAffectedFields() { /* ... */ }
@Override
public QueryCondition getCondition() { /* ... */ }
}Example queries:
SELECT * FROM users WHERE id = 123
INSERT INTO users (name, email) VALUES ('Alice', '[email protected]')
UPDATE users SET email = '[email protected]' WHERE id = 123
DELETE FROM users WHERE id = 123Supporting classes:
-
MySqlConditionParser.java- Parses WHERE clauses -
MySqlFieldsExtractor.java- Extracts field names/values -
MySqlTableNameExtractor.java- Extracts table names -
MySqlValueParser.java- Parses SQL values
import jsi.connection.database.*;
import jsi.connection.database.json.JsonStorageEngine;
import jsi.connection.database.mysql.MySqlServer;
import jsi.connection.database.query.*;
public class DatabaseExample {
public static void main(String[] args) throws Exception {
// 1. Create storage (JSON files in ./database directory)
StorageEngine storage = new JsonStorageEngine("./database");
// 2. Create database engine
DatabaseEngine engine = new DatabaseEngine(storage);
// 3. Initialize sample data
createUsersTable(storage);
// 4. Start server
DatabaseServer server = new MySqlServer(3306, engine);
System.out.println("Database server running on port 3306");
server.start();
}
private static void createUsersTable(StorageEngine storage) throws Exception {
// Create users table with initial data
List<List<Field>> users = Arrays.asList(
Arrays.asList(
new Field("id", "1"),
new Field("name", "Alice"),
new Field("email", "[email protected]")
),
Arrays.asList(
new Field("id", "2"),
new Field("name", "Bob"),
new Field("email", "[email protected]")
)
);
// Note: Direct storage manipulation for setup
// In production, use CREATE TABLE query
for (List<Field> user : users) {
storage.write("users", user);
}
System.out.println("Users table created with " + users.size() + " records");
}
}1. Client sends: "SELECT * FROM users WHERE id = 123"
↓
2. MySqlServer.parseRequest()
→ Creates MySqlQuery object
↓
3. DatabaseServer.handleRequest(query)
→ Delegates to DatabaseEngine
↓
4. DatabaseEngine.execute(query)
→ Identifies READ operation
→ Calls storageEngine.read("users", condition)
↓
5. JsonStorageEngine.read("users", condition)
→ Reads ./database/table_users.json
→ Parses JSON into List<List<Field>>
→ Filters by condition (id = 123)
→ Returns matching records
↓
6. DatabaseEngine wraps results in QueryResult
↓
7. DatabaseServer serializes QueryResult
↓
8. Client receives serialized response
1. Client sends: "INSERT INTO users (id, name) VALUES ('3', 'Charlie')"
↓
2. Parse → MySqlQuery(CREATE, "users", [Field("id", "3"), Field("name", "Charlie")])
↓
3. DatabaseEngine.execute()
→ Identifies CREATE operation
→ Calls storageEngine.write("users", fields)
↓
4. JsonStorageEngine.write()
→ Reads existing ./database/table_users.json
→ Appends new record
→ Writes updated JSON back to file
↓
5. Returns QueryResult(success=true, message="Write successful")
The StorageEngine evaluates WHERE clauses in memory:
private boolean matchesCondition(List<Field> record, QueryCondition condition) {
if (condition == null) return true;
if (condition.isSimpleCondition()) {
// Simple: field = value
return evaluateSimpleCondition(record, condition);
}
if (condition.isComplexCondition()) {
// Complex: (cond1 AND cond2) OR cond3
return evaluateComplexCondition(record, condition);
}
return true;
}
private boolean evaluateSimpleCondition(List<Field> record, QueryCondition condition) {
String fieldName = condition.getFieldName();
Object expectedValue = condition.getValue();
for (Field field : record) {
if (field.getName().equals(fieldName)) {
return compareValues(field.getValue(), expectedValue);
}
}
return false;
}
private boolean evaluateComplexCondition(List<Field> record, QueryCondition condition) {
LogicalOperator operator = condition.getLogicalOperator();
List<QueryCondition> subConditions = condition.getSubConditions();
if (operator == LogicalOperator.AND) {
// All sub-conditions must be true
for (QueryCondition sub : subConditions) {
if (!matchesCondition(record, sub)) return false;
}
return true;
}
else if (operator == LogicalOperator.OR) {
// At least one sub-condition must be true
for (QueryCondition sub : subConditions) {
if (matchesCondition(record, sub)) return true;
}
return false;
}
return true;
}Supported operators:
- Equality:
field = value - Logical AND:
(field1 = value1) AND (field2 = value2) - Logical OR:
(field1 = value1) OR (field2 = value2)
Not yet supported:
- Comparison operators:
>,<,>=,<=,!= - LIKE patterns:
field LIKE '%pattern%' - IN clause:
field IN (value1, value2) - JOINs across tables
The DatabaseEngine defines transaction methods, but leaves implementation to subclasses:
public abstract void beginTransaction();
public abstract void commit();
public abstract void rollback();Example implementation:
public class TransactionalDatabaseEngine extends DatabaseEngine {
private List<Operation> transactionLog = new ArrayList<>();
private boolean inTransaction = false;
@Override
public void beginTransaction() {
transactionLog.clear();
inTransaction = true;
}
@Override
public QueryResult execute(Query query) {
QueryResult result = super.execute(query);
if (inTransaction) {
transactionLog.add(new Operation(query, result));
}
return result;
}
@Override
public void commit() {
// Make all operations permanent
inTransaction = false;
transactionLog.clear();
}
@Override
public void rollback() {
// Undo all operations in reverse order
for (int i = transactionLog.size() - 1; i >= 0; i--) {
undoOperation(transactionLog.get(i));
}
inTransaction = false;
transactionLog.clear();
}
}-
Storage: Limited by file system and parsing overhead
- JSON/XML: Parses entire file on each query
- No indexing: O(n) scan for every query
-
Concurrency: Thread-per-connection model
- Each client gets own thread
- No connection pooling
- Limited to ~10,000 concurrent connections
- In-memory caching: Load collections once, keep in memory
- Indexing: Build B-tree indexes for fast lookups
- Binary storage: Faster than JSON/XML parsing
- Connection pooling: Reuse threads
- Asynchronous I/O: Non-blocking operations
- Architecture Overview - Three-layer design philosophy
-
Core Abstractions -
Request,Responseinterfaces - ConnectionServer - TCP transport layer
- HTTP Server - Alternative protocol implementation
- Getting Started - Quick start examples
Next: Explore Extensibility Guide to learn how to create custom storage engines and query parsers.
JSI - Java Server Interface | Educational Server Framework | Zero Dependencies
Home • Getting Started • Architecture • Source Code
Made for learning | Report Issues • Discussions
Last updated: December 2025 | JSI v1.0
HTTP Development
Database Development
Custom Protocols