16 changed files with 5477 additions and 0 deletions
			
			
		- 
					338jdbc-driver/README.md
 - 
					308jdbc-driver/examples/SeaweedFSJDBCExample.java
 - 
					154jdbc-driver/pom.xml
 - 
					497jdbc-driver/src/main/java/com/seaweedfs/jdbc/SeaweedFSConnection.java
 - 
					71jdbc-driver/src/main/java/com/seaweedfs/jdbc/SeaweedFSConnectionInfo.java
 - 
					972jdbc-driver/src/main/java/com/seaweedfs/jdbc/SeaweedFSDatabaseMetaData.java
 - 
					207jdbc-driver/src/main/java/com/seaweedfs/jdbc/SeaweedFSDriver.java
 - 
					352jdbc-driver/src/main/java/com/seaweedfs/jdbc/SeaweedFSPreparedStatement.java
 - 
					1245jdbc-driver/src/main/java/com/seaweedfs/jdbc/SeaweedFSResultSet.java
 - 
					202jdbc-driver/src/main/java/com/seaweedfs/jdbc/SeaweedFSResultSetMetaData.java
 - 
					389jdbc-driver/src/main/java/com/seaweedfs/jdbc/SeaweedFSStatement.java
 - 
					1jdbc-driver/src/main/resources/META-INF/services/java.sql.Driver
 - 
					75jdbc-driver/src/test/java/com/seaweedfs/jdbc/SeaweedFSDriverTest.java
 - 
					1weed/command/command.go
 - 
					141weed/command/jdbc.go
 - 
					524weed/server/jdbc_server.go
 
@ -0,0 +1,338 @@ | 
				
			|||
# SeaweedFS JDBC Driver | 
				
			|||
 | 
				
			|||
A JDBC driver for connecting to SeaweedFS SQL engine, enabling standard Java applications and BI tools to query SeaweedFS MQ topics using SQL. | 
				
			|||
 | 
				
			|||
## Features | 
				
			|||
 | 
				
			|||
- **Standard JDBC Interface**: Compatible with any Java application or tool that supports JDBC | 
				
			|||
- **SQL Query Support**: Execute SELECT queries on SeaweedFS MQ topics | 
				
			|||
- **Aggregation Functions**: Support for COUNT, SUM, AVG, MIN, MAX operations | 
				
			|||
- **System Columns**: Access to `_timestamp_ns`, `_key`, `_source` system columns | 
				
			|||
- **Database Tools**: Works with DBeaver, IntelliJ DataGrip, and other database tools | 
				
			|||
- **BI Tools**: Compatible with Tableau, Power BI, and other business intelligence tools | 
				
			|||
- **Read-Only Access**: Secure read-only access to your SeaweedFS data | 
				
			|||
 | 
				
			|||
## Quick Start | 
				
			|||
 | 
				
			|||
### 1. Start SeaweedFS JDBC Server | 
				
			|||
 | 
				
			|||
First, start the SeaweedFS JDBC server: | 
				
			|||
 | 
				
			|||
```bash | 
				
			|||
# Start JDBC server on default port 8089 | 
				
			|||
weed jdbc | 
				
			|||
 | 
				
			|||
# Or with custom configuration | 
				
			|||
weed jdbc -port=8090 -host=0.0.0.0 -master=master-server:9333 | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
### 2. Add JDBC Driver to Your Project | 
				
			|||
 | 
				
			|||
#### Maven | 
				
			|||
 | 
				
			|||
```xml | 
				
			|||
<dependency> | 
				
			|||
    <groupId>com.seaweedfs</groupId> | 
				
			|||
    <artifactId>seaweedfs-jdbc</artifactId> | 
				
			|||
    <version>1.0.0</version> | 
				
			|||
</dependency> | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
#### Gradle | 
				
			|||
 | 
				
			|||
```gradle | 
				
			|||
implementation 'com.seaweedfs:seaweedfs-jdbc:1.0.0' | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
### 3. Connect and Query | 
				
			|||
 | 
				
			|||
```java | 
				
			|||
import java.sql.*; | 
				
			|||
 | 
				
			|||
public class SeaweedFSExample { | 
				
			|||
    public static void main(String[] args) throws SQLException { | 
				
			|||
        // JDBC URL format: jdbc:seaweedfs://host:port/database | 
				
			|||
        String url = "jdbc:seaweedfs://localhost:8089/default"; | 
				
			|||
         | 
				
			|||
        // Connect to SeaweedFS | 
				
			|||
        Connection conn = DriverManager.getConnection(url); | 
				
			|||
         | 
				
			|||
        // Execute queries | 
				
			|||
        Statement stmt = conn.createStatement(); | 
				
			|||
        ResultSet rs = stmt.executeQuery("SELECT * FROM my_topic LIMIT 10"); | 
				
			|||
         | 
				
			|||
        // Process results | 
				
			|||
        while (rs.next()) { | 
				
			|||
            System.out.println("ID: " + rs.getLong("id")); | 
				
			|||
            System.out.println("Message: " + rs.getString("message")); | 
				
			|||
            System.out.println("Timestamp: " + rs.getTimestamp("_timestamp_ns")); | 
				
			|||
        } | 
				
			|||
         | 
				
			|||
        // Clean up | 
				
			|||
        rs.close(); | 
				
			|||
        stmt.close(); | 
				
			|||
        conn.close(); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
## JDBC URL Format | 
				
			|||
 | 
				
			|||
``` | 
				
			|||
jdbc:seaweedfs://host:port/database[?property=value&...] | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
### Parameters | 
				
			|||
 | 
				
			|||
| Parameter | Default | Description | | 
				
			|||
|-----------|---------|-------------| | 
				
			|||
| `host` | localhost | SeaweedFS JDBC server hostname | | 
				
			|||
| `port` | 8089 | SeaweedFS JDBC server port | | 
				
			|||
| `database` | default | Database/namespace name | | 
				
			|||
| `connectTimeout` | 30000 | Connection timeout in milliseconds | | 
				
			|||
| `socketTimeout` | 0 | Socket timeout in milliseconds (0 = infinite) | | 
				
			|||
 | 
				
			|||
### Examples | 
				
			|||
 | 
				
			|||
```java | 
				
			|||
// Basic connection | 
				
			|||
"jdbc:seaweedfs://localhost:8089/default" | 
				
			|||
 | 
				
			|||
// Custom host and port | 
				
			|||
"jdbc:seaweedfs://seaweed-server:9000/production" | 
				
			|||
 | 
				
			|||
// With query parameters | 
				
			|||
"jdbc:seaweedfs://localhost:8089/default?connectTimeout=5000&socketTimeout=30000" | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
## Supported SQL Operations | 
				
			|||
 | 
				
			|||
### SELECT Queries | 
				
			|||
```sql | 
				
			|||
-- Basic select | 
				
			|||
SELECT * FROM topic_name; | 
				
			|||
 | 
				
			|||
-- With WHERE clause | 
				
			|||
SELECT id, message FROM topic_name WHERE id > 1000; | 
				
			|||
 | 
				
			|||
-- With LIMIT | 
				
			|||
SELECT * FROM topic_name ORDER BY _timestamp_ns DESC LIMIT 100; | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
### Aggregation Functions | 
				
			|||
```sql | 
				
			|||
-- Count records | 
				
			|||
SELECT COUNT(*) FROM topic_name; | 
				
			|||
 | 
				
			|||
-- Aggregations | 
				
			|||
SELECT  | 
				
			|||
    COUNT(*) as total_messages, | 
				
			|||
    MIN(id) as min_id, | 
				
			|||
    MAX(id) as max_id, | 
				
			|||
    AVG(amount) as avg_amount | 
				
			|||
FROM topic_name; | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
### System Columns | 
				
			|||
```sql | 
				
			|||
-- Access system columns | 
				
			|||
SELECT  | 
				
			|||
    id,  | 
				
			|||
    message, | 
				
			|||
    _timestamp_ns as timestamp, | 
				
			|||
    _key as partition_key, | 
				
			|||
    _source as data_source | 
				
			|||
FROM topic_name; | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
### Schema Information | 
				
			|||
```sql | 
				
			|||
-- List databases | 
				
			|||
SHOW DATABASES; | 
				
			|||
 | 
				
			|||
-- List tables in current database | 
				
			|||
SHOW TABLES; | 
				
			|||
 | 
				
			|||
-- Describe table structure | 
				
			|||
DESCRIBE topic_name; | 
				
			|||
-- or | 
				
			|||
DESC topic_name; | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
## Database Tool Integration | 
				
			|||
 | 
				
			|||
### DBeaver | 
				
			|||
 | 
				
			|||
1. Download and install DBeaver | 
				
			|||
2. Create new connection → Generic JDBC | 
				
			|||
3. Settings: | 
				
			|||
   - **URL**: `jdbc:seaweedfs://localhost:8089/default` | 
				
			|||
   - **Driver Class**: `com.seaweedfs.jdbc.SeaweedFSDriver` | 
				
			|||
   - **Libraries**: Add `seaweedfs-jdbc-1.0.0.jar` | 
				
			|||
 | 
				
			|||
### IntelliJ DataGrip | 
				
			|||
 | 
				
			|||
1. Open DataGrip | 
				
			|||
2. Add New Data Source → Generic | 
				
			|||
3. Configure: | 
				
			|||
   - **URL**: `jdbc:seaweedfs://localhost:8089/default` | 
				
			|||
   - **Driver**: Add `seaweedfs-jdbc-1.0.0.jar` | 
				
			|||
   - **Driver Class**: `com.seaweedfs.jdbc.SeaweedFSDriver` | 
				
			|||
 | 
				
			|||
### Tableau | 
				
			|||
 | 
				
			|||
1. Connect to Data → More... → Generic JDBC | 
				
			|||
2. Configure: | 
				
			|||
   - **URL**: `jdbc:seaweedfs://localhost:8089/default` | 
				
			|||
   - **Driver Path**: Path to `seaweedfs-jdbc-1.0.0.jar` | 
				
			|||
   - **Class Name**: `com.seaweedfs.jdbc.SeaweedFSDriver` | 
				
			|||
 | 
				
			|||
## Advanced Usage | 
				
			|||
 | 
				
			|||
### Connection Pooling | 
				
			|||
 | 
				
			|||
```java | 
				
			|||
import com.zaxxer.hikari.HikariConfig; | 
				
			|||
import com.zaxxer.hikari.HikariDataSource; | 
				
			|||
 | 
				
			|||
HikariConfig config = new HikariConfig(); | 
				
			|||
config.setJdbcUrl("jdbc:seaweedfs://localhost:8089/default"); | 
				
			|||
config.setMaximumPoolSize(10); | 
				
			|||
 | 
				
			|||
HikariDataSource dataSource = new HikariDataSource(config); | 
				
			|||
Connection conn = dataSource.getConnection(); | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
### PreparedStatements | 
				
			|||
 | 
				
			|||
```java | 
				
			|||
String sql = "SELECT * FROM topic_name WHERE id > ? AND created_date > ?"; | 
				
			|||
PreparedStatement stmt = conn.prepareStatement(sql); | 
				
			|||
stmt.setLong(1, 1000); | 
				
			|||
stmt.setTimestamp(2, Timestamp.valueOf("2024-01-01 00:00:00")); | 
				
			|||
 | 
				
			|||
ResultSet rs = stmt.executeQuery(); | 
				
			|||
while (rs.next()) { | 
				
			|||
    // Process results | 
				
			|||
} | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
### Metadata Access | 
				
			|||
 | 
				
			|||
```java | 
				
			|||
DatabaseMetaData metadata = conn.getMetaData(); | 
				
			|||
 | 
				
			|||
// Get database information | 
				
			|||
System.out.println("Database: " + metadata.getDatabaseProductName()); | 
				
			|||
System.out.println("Version: " + metadata.getDatabaseProductVersion()); | 
				
			|||
System.out.println("Driver: " + metadata.getDriverName()); | 
				
			|||
 | 
				
			|||
// Get table information | 
				
			|||
ResultSet tables = metadata.getTables(null, null, null, null); | 
				
			|||
while (tables.next()) { | 
				
			|||
    System.out.println("Table: " + tables.getString("TABLE_NAME")); | 
				
			|||
} | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
## Building from Source | 
				
			|||
 | 
				
			|||
```bash | 
				
			|||
# Clone the repository | 
				
			|||
git clone https://github.com/seaweedfs/seaweedfs.git | 
				
			|||
cd seaweedfs/jdbc-driver | 
				
			|||
 | 
				
			|||
# Build with Maven | 
				
			|||
mvn clean package | 
				
			|||
 | 
				
			|||
# Run tests | 
				
			|||
mvn test | 
				
			|||
 | 
				
			|||
# Install to local repository | 
				
			|||
mvn install | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
## Configuration | 
				
			|||
 | 
				
			|||
### Server-Side Configuration | 
				
			|||
 | 
				
			|||
The JDBC server supports the following command-line options: | 
				
			|||
 | 
				
			|||
```bash | 
				
			|||
weed jdbc -help | 
				
			|||
  -host string | 
				
			|||
        JDBC server host (default "localhost") | 
				
			|||
  -master string | 
				
			|||
        SeaweedFS master server address (default "localhost:9333") | 
				
			|||
  -port int | 
				
			|||
        JDBC server port (default 8089) | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
### Client-Side Configuration | 
				
			|||
 | 
				
			|||
Connection properties can be set via URL parameters or Properties object: | 
				
			|||
 | 
				
			|||
```java | 
				
			|||
Properties props = new Properties(); | 
				
			|||
props.setProperty("connectTimeout", "10000"); | 
				
			|||
props.setProperty("socketTimeout", "30000"); | 
				
			|||
 | 
				
			|||
Connection conn = DriverManager.getConnection( | 
				
			|||
    "jdbc:seaweedfs://localhost:8089/default", props); | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
## Performance Tips | 
				
			|||
 | 
				
			|||
1. **Use LIMIT clauses**: Always limit result sets for large topics | 
				
			|||
2. **Filter early**: Use WHERE clauses to reduce data transfer | 
				
			|||
3. **Connection pooling**: Use connection pools for multi-threaded applications | 
				
			|||
4. **Batch operations**: Use batch statements for multiple queries | 
				
			|||
5. **Close resources**: Always close ResultSets, Statements, and Connections | 
				
			|||
 | 
				
			|||
## Limitations | 
				
			|||
 | 
				
			|||
- **Read-Only**: SeaweedFS JDBC driver only supports SELECT operations | 
				
			|||
- **No Transactions**: Transaction support is not available | 
				
			|||
- **Single Table**: Joins between tables are not supported | 
				
			|||
- **Limited SQL**: Only basic SQL SELECT syntax is supported | 
				
			|||
 | 
				
			|||
## Troubleshooting | 
				
			|||
 | 
				
			|||
### Connection Issues | 
				
			|||
 | 
				
			|||
```bash | 
				
			|||
# Test JDBC server connectivity | 
				
			|||
telnet localhost 8089 | 
				
			|||
 | 
				
			|||
# Check SeaweedFS master connectivity | 
				
			|||
weed shell | 
				
			|||
> cluster.status | 
				
			|||
``` | 
				
			|||
 | 
				
			|||
### Common Errors | 
				
			|||
 | 
				
			|||
**Error: "Connection refused"** | 
				
			|||
- Ensure JDBC server is running on the specified host/port | 
				
			|||
- Check firewall settings | 
				
			|||
 | 
				
			|||
**Error: "No suitable driver found"** | 
				
			|||
- Verify JDBC driver is in classpath | 
				
			|||
- Ensure correct driver class name: `com.seaweedfs.jdbc.SeaweedFSDriver` | 
				
			|||
 | 
				
			|||
**Error: "Topic not found"** | 
				
			|||
- Verify topic exists in SeaweedFS | 
				
			|||
- Check database/namespace name in connection URL | 
				
			|||
 | 
				
			|||
## Contributing | 
				
			|||
 | 
				
			|||
Contributions are welcome! Please see the main SeaweedFS repository for contribution guidelines. | 
				
			|||
 | 
				
			|||
## License | 
				
			|||
 | 
				
			|||
This JDBC driver is part of SeaweedFS and is licensed under the Apache License 2.0. | 
				
			|||
 | 
				
			|||
## Support | 
				
			|||
 | 
				
			|||
- **Documentation**: [SeaweedFS Wiki](https://github.com/seaweedfs/seaweedfs/wiki) | 
				
			|||
- **Issues**: [GitHub Issues](https://github.com/seaweedfs/seaweedfs/issues) | 
				
			|||
- **Discussions**: [GitHub Discussions](https://github.com/seaweedfs/seaweedfs/discussions) | 
				
			|||
- **Chat**: [SeaweedFS Slack](https://join.slack.com/t/seaweedfs/shared_invite/...) | 
				
			|||
@ -0,0 +1,308 @@ | 
				
			|||
package com.seaweedfs.jdbc.examples; | 
				
			|||
 | 
				
			|||
import java.sql.*; | 
				
			|||
import java.util.Properties; | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * Complete example demonstrating SeaweedFS JDBC driver usage | 
				
			|||
 */ | 
				
			|||
public class SeaweedFSJDBCExample { | 
				
			|||
     | 
				
			|||
    public static void main(String[] args) { | 
				
			|||
        // JDBC URL for SeaweedFS | 
				
			|||
        String url = "jdbc:seaweedfs://localhost:8089/default"; | 
				
			|||
         | 
				
			|||
        try { | 
				
			|||
            // 1. Load the driver (optional - auto-registration via META-INF/services) | 
				
			|||
            Class.forName("com.seaweedfs.jdbc.SeaweedFSDriver"); | 
				
			|||
            System.out.println("✓ SeaweedFS JDBC Driver loaded successfully"); | 
				
			|||
             | 
				
			|||
            // 2. Connect to SeaweedFS | 
				
			|||
            System.out.println("\n📡 Connecting to SeaweedFS..."); | 
				
			|||
            Connection conn = DriverManager.getConnection(url); | 
				
			|||
            System.out.println("✓ Connected to: " + url); | 
				
			|||
             | 
				
			|||
            // 3. Get database metadata | 
				
			|||
            DatabaseMetaData dbMeta = conn.getMetaData(); | 
				
			|||
            System.out.println("\n📊 Database Information:"); | 
				
			|||
            System.out.println("  Database: " + dbMeta.getDatabaseProductName()); | 
				
			|||
            System.out.println("  Version: " + dbMeta.getDatabaseProductVersion()); | 
				
			|||
            System.out.println("  Driver: " + dbMeta.getDriverName() + " v" + dbMeta.getDriverVersion()); | 
				
			|||
            System.out.println("  JDBC Version: " + dbMeta.getJDBCMajorVersion() + "." + dbMeta.getJDBCMinorVersion()); | 
				
			|||
            System.out.println("  Read-only: " + dbMeta.isReadOnly()); | 
				
			|||
             | 
				
			|||
            // 4. List available databases/schemas | 
				
			|||
            System.out.println("\n🗄️  Available Databases:"); | 
				
			|||
            ResultSet catalogs = dbMeta.getCatalogs(); | 
				
			|||
            while (catalogs.next()) { | 
				
			|||
                System.out.println("  • " + catalogs.getString("TABLE_CAT")); | 
				
			|||
            } | 
				
			|||
            catalogs.close(); | 
				
			|||
             | 
				
			|||
            // 5. Execute basic queries | 
				
			|||
            System.out.println("\n🔍 Executing SQL Queries:"); | 
				
			|||
             | 
				
			|||
            Statement stmt = conn.createStatement(); | 
				
			|||
             | 
				
			|||
            // Show databases | 
				
			|||
            System.out.println("\n  📋 SHOW DATABASES:"); | 
				
			|||
            ResultSet rs = stmt.executeQuery("SHOW DATABASES"); | 
				
			|||
            while (rs.next()) { | 
				
			|||
                System.out.println("    " + rs.getString(1)); | 
				
			|||
            } | 
				
			|||
            rs.close(); | 
				
			|||
             | 
				
			|||
            // Show tables (topics) | 
				
			|||
            System.out.println("\n  📋 SHOW TABLES:"); | 
				
			|||
            rs = stmt.executeQuery("SHOW TABLES"); | 
				
			|||
            ResultSetMetaData rsmd = rs.getMetaData(); | 
				
			|||
            int columnCount = rsmd.getColumnCount(); | 
				
			|||
             | 
				
			|||
            // Print headers | 
				
			|||
            for (int i = 1; i <= columnCount; i++) { | 
				
			|||
                System.out.print(String.format("%-20s", rsmd.getColumnName(i))); | 
				
			|||
            } | 
				
			|||
            System.out.println(); | 
				
			|||
            System.out.println("-".repeat(20 * columnCount)); | 
				
			|||
             | 
				
			|||
            // Print rows | 
				
			|||
            while (rs.next()) { | 
				
			|||
                for (int i = 1; i <= columnCount; i++) { | 
				
			|||
                    System.out.print(String.format("%-20s", rs.getString(i))); | 
				
			|||
                } | 
				
			|||
                System.out.println(); | 
				
			|||
            } | 
				
			|||
            rs.close(); | 
				
			|||
             | 
				
			|||
            // 6. Query a specific topic (if exists) | 
				
			|||
            String topicQuery = "SELECT * FROM test_topic LIMIT 5"; | 
				
			|||
            System.out.println("\n  📋 " + topicQuery + ":"); | 
				
			|||
             | 
				
			|||
            try { | 
				
			|||
                rs = stmt.executeQuery(topicQuery); | 
				
			|||
                rsmd = rs.getMetaData(); | 
				
			|||
                columnCount = rsmd.getColumnCount(); | 
				
			|||
                 | 
				
			|||
                // Print column headers | 
				
			|||
                for (int i = 1; i <= columnCount; i++) { | 
				
			|||
                    System.out.print(String.format("%-15s", rsmd.getColumnName(i))); | 
				
			|||
                } | 
				
			|||
                System.out.println(); | 
				
			|||
                System.out.println("-".repeat(15 * columnCount)); | 
				
			|||
                 | 
				
			|||
                // Print data rows | 
				
			|||
                int rowCount = 0; | 
				
			|||
                while (rs.next() && rowCount < 5) { | 
				
			|||
                    for (int i = 1; i <= columnCount; i++) { | 
				
			|||
                        String value = rs.getString(i); | 
				
			|||
                        if (value != null && value.length() > 12) { | 
				
			|||
                            value = value.substring(0, 12) + "..."; | 
				
			|||
                        } | 
				
			|||
                        System.out.print(String.format("%-15s", value != null ? value : "NULL")); | 
				
			|||
                    } | 
				
			|||
                    System.out.println(); | 
				
			|||
                    rowCount++; | 
				
			|||
                } | 
				
			|||
                 | 
				
			|||
                if (rowCount == 0) { | 
				
			|||
                    System.out.println("    (No data found)"); | 
				
			|||
                } | 
				
			|||
                 | 
				
			|||
                rs.close(); | 
				
			|||
            } catch (SQLException e) { | 
				
			|||
                System.out.println("    ⚠️  Topic 'test_topic' not found: " + e.getMessage()); | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            // 7. Demonstrate aggregation queries | 
				
			|||
            System.out.println("\n  🧮 Aggregation Example:"); | 
				
			|||
            try { | 
				
			|||
                rs = stmt.executeQuery("SELECT COUNT(*) as total_records FROM test_topic"); | 
				
			|||
                if (rs.next()) { | 
				
			|||
                    System.out.println("    Total records: " + rs.getLong("total_records")); | 
				
			|||
                } | 
				
			|||
                rs.close(); | 
				
			|||
            } catch (SQLException e) { | 
				
			|||
                System.out.println("    ⚠️  Aggregation example skipped: " + e.getMessage()); | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            // 8. Demonstrate PreparedStatement | 
				
			|||
            System.out.println("\n  📝 PreparedStatement Example:"); | 
				
			|||
            String preparedQuery = "SELECT * FROM test_topic WHERE id > ? LIMIT ?"; | 
				
			|||
             | 
				
			|||
            try { | 
				
			|||
                PreparedStatement pstmt = conn.prepareStatement(preparedQuery); | 
				
			|||
                pstmt.setLong(1, 100); | 
				
			|||
                pstmt.setInt(2, 3); | 
				
			|||
                 | 
				
			|||
                System.out.println("    Query: " + preparedQuery); | 
				
			|||
                System.out.println("    Parameters: id > 100, LIMIT 3"); | 
				
			|||
                 | 
				
			|||
                rs = pstmt.executeQuery(); | 
				
			|||
                rsmd = rs.getMetaData(); | 
				
			|||
                columnCount = rsmd.getColumnCount(); | 
				
			|||
                 | 
				
			|||
                int count = 0; | 
				
			|||
                while (rs.next()) { | 
				
			|||
                    if (count == 0) { | 
				
			|||
                        // Print headers for first row | 
				
			|||
                        for (int i = 1; i <= columnCount; i++) { | 
				
			|||
                            System.out.print(String.format("%-15s", rsmd.getColumnName(i))); | 
				
			|||
                        } | 
				
			|||
                        System.out.println(); | 
				
			|||
                        System.out.println("-".repeat(15 * columnCount)); | 
				
			|||
                    } | 
				
			|||
                     | 
				
			|||
                    for (int i = 1; i <= columnCount; i++) { | 
				
			|||
                        String value = rs.getString(i); | 
				
			|||
                        if (value != null && value.length() > 12) { | 
				
			|||
                            value = value.substring(0, 12) + "..."; | 
				
			|||
                        } | 
				
			|||
                        System.out.print(String.format("%-15s", value != null ? value : "NULL")); | 
				
			|||
                    } | 
				
			|||
                    System.out.println(); | 
				
			|||
                    count++; | 
				
			|||
                } | 
				
			|||
                 | 
				
			|||
                if (count == 0) { | 
				
			|||
                    System.out.println("    (No records match criteria)"); | 
				
			|||
                } | 
				
			|||
                 | 
				
			|||
                rs.close(); | 
				
			|||
                pstmt.close(); | 
				
			|||
            } catch (SQLException e) { | 
				
			|||
                System.out.println("    ⚠️  PreparedStatement example skipped: " + e.getMessage()); | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            // 9. System columns example | 
				
			|||
            System.out.println("\n  🔧 System Columns Example:"); | 
				
			|||
            try { | 
				
			|||
                rs = stmt.executeQuery("SELECT id, _timestamp_ns, _key, _source FROM test_topic LIMIT 3"); | 
				
			|||
                rsmd = rs.getMetaData(); | 
				
			|||
                columnCount = rsmd.getColumnCount(); | 
				
			|||
                 | 
				
			|||
                // Print headers | 
				
			|||
                for (int i = 1; i <= columnCount; i++) { | 
				
			|||
                    System.out.print(String.format("%-20s", rsmd.getColumnName(i))); | 
				
			|||
                } | 
				
			|||
                System.out.println(); | 
				
			|||
                System.out.println("-".repeat(20 * columnCount)); | 
				
			|||
                 | 
				
			|||
                int count = 0; | 
				
			|||
                while (rs.next()) { | 
				
			|||
                    for (int i = 1; i <= columnCount; i++) { | 
				
			|||
                        String value = rs.getString(i); | 
				
			|||
                        if (value != null && value.length() > 17) { | 
				
			|||
                            value = value.substring(0, 17) + "..."; | 
				
			|||
                        } | 
				
			|||
                        System.out.print(String.format("%-20s", value != null ? value : "NULL")); | 
				
			|||
                    } | 
				
			|||
                    System.out.println(); | 
				
			|||
                    count++; | 
				
			|||
                } | 
				
			|||
                 | 
				
			|||
                if (count == 0) { | 
				
			|||
                    System.out.println("    (No data available for system columns demo)"); | 
				
			|||
                } | 
				
			|||
                 | 
				
			|||
                rs.close(); | 
				
			|||
            } catch (SQLException e) { | 
				
			|||
                System.out.println("    ⚠️  System columns example skipped: " + e.getMessage()); | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            // 10. Connection properties example | 
				
			|||
            System.out.println("\n⚙️  Connection Properties:"); | 
				
			|||
            System.out.println("  Auto-commit: " + conn.getAutoCommit()); | 
				
			|||
            System.out.println("  Read-only: " + conn.isReadOnly()); | 
				
			|||
            System.out.println("  Transaction isolation: " + conn.getTransactionIsolation()); | 
				
			|||
            System.out.println("  Catalog: " + conn.getCatalog()); | 
				
			|||
             | 
				
			|||
            // 11. Clean up | 
				
			|||
            stmt.close(); | 
				
			|||
            conn.close(); | 
				
			|||
             | 
				
			|||
            System.out.println("\n✅ SeaweedFS JDBC Example completed successfully!"); | 
				
			|||
            System.out.println("\n💡 Next Steps:"); | 
				
			|||
            System.out.println("  • Try connecting with DBeaver or other JDBC tools"); | 
				
			|||
            System.out.println("  • Use in your Java applications with connection pooling"); | 
				
			|||
            System.out.println("  • Integrate with BI tools like Tableau or Power BI"); | 
				
			|||
            System.out.println("  • Build data pipelines using SeaweedFS as a data source"); | 
				
			|||
             | 
				
			|||
        } catch (ClassNotFoundException e) { | 
				
			|||
            System.err.println("❌ SeaweedFS JDBC Driver not found: " + e.getMessage()); | 
				
			|||
            System.err.println("   Make sure seaweedfs-jdbc.jar is in your classpath"); | 
				
			|||
        } catch (SQLException e) { | 
				
			|||
            System.err.println("❌ Database error: " + e.getMessage()); | 
				
			|||
            System.err.println("   Make sure SeaweedFS JDBC server is running:"); | 
				
			|||
            System.err.println("   weed jdbc -port=8089 -master=localhost:9333"); | 
				
			|||
        } catch (Exception e) { | 
				
			|||
            System.err.println("❌ Unexpected error: " + e.getMessage()); | 
				
			|||
            e.printStackTrace(); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    /** | 
				
			|||
     * Example with connection pooling using HikariCP | 
				
			|||
     */ | 
				
			|||
    public static void connectionPoolingExample() { | 
				
			|||
        try { | 
				
			|||
            // This would require HikariCP dependency | 
				
			|||
            /* | 
				
			|||
            HikariConfig config = new HikariConfig(); | 
				
			|||
            config.setJdbcUrl("jdbc:seaweedfs://localhost:8089/default"); | 
				
			|||
            config.setMaximumPoolSize(10); | 
				
			|||
            config.setMinimumIdle(2); | 
				
			|||
            config.setConnectionTimeout(30000); | 
				
			|||
            config.setIdleTimeout(600000); | 
				
			|||
             | 
				
			|||
            HikariDataSource dataSource = new HikariDataSource(config); | 
				
			|||
             | 
				
			|||
            try (Connection conn = dataSource.getConnection()) { | 
				
			|||
                // Use connection from pool | 
				
			|||
                Statement stmt = conn.createStatement(); | 
				
			|||
                ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM my_topic"); | 
				
			|||
                if (rs.next()) { | 
				
			|||
                    System.out.println("Record count: " + rs.getLong(1)); | 
				
			|||
                } | 
				
			|||
                rs.close(); | 
				
			|||
                stmt.close(); | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            dataSource.close(); | 
				
			|||
            */ | 
				
			|||
             | 
				
			|||
            System.out.println("Connection pooling example (commented out - requires HikariCP dependency)"); | 
				
			|||
        } catch (Exception e) { | 
				
			|||
            e.printStackTrace(); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    /** | 
				
			|||
     * Example configuration for different database tools | 
				
			|||
     */ | 
				
			|||
    public static void printToolConfiguration() { | 
				
			|||
        System.out.println("\n🛠️  Database Tool Configuration:"); | 
				
			|||
         | 
				
			|||
        System.out.println("\n📊 DBeaver:"); | 
				
			|||
        System.out.println("  1. New Connection → Generic JDBC"); | 
				
			|||
        System.out.println("  2. URL: jdbc:seaweedfs://localhost:8089/default"); | 
				
			|||
        System.out.println("  3. Driver Class: com.seaweedfs.jdbc.SeaweedFSDriver"); | 
				
			|||
        System.out.println("  4. Add seaweedfs-jdbc.jar to Libraries"); | 
				
			|||
         | 
				
			|||
        System.out.println("\n💻 IntelliJ DataGrip:"); | 
				
			|||
        System.out.println("  1. New Data Source → Generic"); | 
				
			|||
        System.out.println("  2. URL: jdbc:seaweedfs://localhost:8089/default"); | 
				
			|||
        System.out.println("  3. Add Driver: seaweedfs-jdbc.jar"); | 
				
			|||
        System.out.println("  4. Class: com.seaweedfs.jdbc.SeaweedFSDriver"); | 
				
			|||
         | 
				
			|||
        System.out.println("\n📈 Tableau:"); | 
				
			|||
        System.out.println("  1. Connect to Data → More... → Generic JDBC"); | 
				
			|||
        System.out.println("  2. URL: jdbc:seaweedfs://localhost:8089/default"); | 
				
			|||
        System.out.println("  3. Driver Path: /path/to/seaweedfs-jdbc.jar"); | 
				
			|||
        System.out.println("  4. Class Name: com.seaweedfs.jdbc.SeaweedFSDriver"); | 
				
			|||
         | 
				
			|||
        System.out.println("\n☕ Java Application:"); | 
				
			|||
        System.out.println("  Class.forName(\"com.seaweedfs.jdbc.SeaweedFSDriver\");"); | 
				
			|||
        System.out.println("  Connection conn = DriverManager.getConnection("); | 
				
			|||
        System.out.println("      \"jdbc:seaweedfs://localhost:8089/default\");"); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,154 @@ | 
				
			|||
<?xml version="1.0" encoding="UTF-8"?> | 
				
			|||
<project xmlns="http://maven.apache.org/POM/4.0.0" | 
				
			|||
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | 
				
			|||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0  | 
				
			|||
         http://maven.apache.org/xsd/maven-4.0.0.xsd"> | 
				
			|||
    <modelVersion>4.0.0</modelVersion> | 
				
			|||
 | 
				
			|||
    <groupId>com.seaweedfs</groupId> | 
				
			|||
    <artifactId>seaweedfs-jdbc</artifactId> | 
				
			|||
    <version>1.0.0</version> | 
				
			|||
    <packaging>jar</packaging> | 
				
			|||
 | 
				
			|||
    <name>SeaweedFS JDBC Driver</name> | 
				
			|||
    <description>JDBC driver for connecting to SeaweedFS SQL engine</description> | 
				
			|||
    <url>https://github.com/seaweedfs/seaweedfs</url> | 
				
			|||
 | 
				
			|||
    <licenses> | 
				
			|||
        <license> | 
				
			|||
            <name>Apache License, Version 2.0</name> | 
				
			|||
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> | 
				
			|||
            <distribution>repo</distribution> | 
				
			|||
        </license> | 
				
			|||
    </licenses> | 
				
			|||
 | 
				
			|||
    <properties> | 
				
			|||
        <maven.compiler.source>8</maven.compiler.source> | 
				
			|||
        <maven.compiler.target>8</maven.compiler.target> | 
				
			|||
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | 
				
			|||
        <junit.version>5.9.0</junit.version> | 
				
			|||
    </properties> | 
				
			|||
 | 
				
			|||
    <dependencies> | 
				
			|||
        <!-- JUnit for testing --> | 
				
			|||
        <dependency> | 
				
			|||
            <groupId>org.junit.jupiter</groupId> | 
				
			|||
            <artifactId>junit-jupiter-engine</artifactId> | 
				
			|||
            <version>${junit.version}</version> | 
				
			|||
            <scope>test</scope> | 
				
			|||
        </dependency> | 
				
			|||
        <dependency> | 
				
			|||
            <groupId>org.junit.jupiter</groupId> | 
				
			|||
            <artifactId>junit-jupiter-api</artifactId> | 
				
			|||
            <version>${junit.version}</version> | 
				
			|||
            <scope>test</scope> | 
				
			|||
        </dependency> | 
				
			|||
 | 
				
			|||
        <!-- SLF4J for logging --> | 
				
			|||
        <dependency> | 
				
			|||
            <groupId>org.slf4j</groupId> | 
				
			|||
            <artifactId>slf4j-api</artifactId> | 
				
			|||
            <version>1.7.36</version> | 
				
			|||
        </dependency> | 
				
			|||
        <dependency> | 
				
			|||
            <groupId>org.slf4j</groupId> | 
				
			|||
            <artifactId>slf4j-simple</artifactId> | 
				
			|||
            <version>1.7.36</version> | 
				
			|||
            <scope>test</scope> | 
				
			|||
        </dependency> | 
				
			|||
    </dependencies> | 
				
			|||
 | 
				
			|||
    <build> | 
				
			|||
        <plugins> | 
				
			|||
            <plugin> | 
				
			|||
                <groupId>org.apache.maven.plugins</groupId> | 
				
			|||
                <artifactId>maven-compiler-plugin</artifactId> | 
				
			|||
                <version>3.10.1</version> | 
				
			|||
                <configuration> | 
				
			|||
                    <source>8</source> | 
				
			|||
                    <target>8</target> | 
				
			|||
                    <encoding>UTF-8</encoding> | 
				
			|||
                </configuration> | 
				
			|||
            </plugin> | 
				
			|||
 | 
				
			|||
            <plugin> | 
				
			|||
                <groupId>org.apache.maven.plugins</groupId> | 
				
			|||
                <artifactId>maven-surefire-plugin</artifactId> | 
				
			|||
                <version>3.0.0-M7</version> | 
				
			|||
                <configuration> | 
				
			|||
                    <useSystemClassLoader>false</useSystemClassLoader> | 
				
			|||
                </configuration> | 
				
			|||
            </plugin> | 
				
			|||
 | 
				
			|||
            <plugin> | 
				
			|||
                <groupId>org.apache.maven.plugins</groupId> | 
				
			|||
                <artifactId>maven-jar-plugin</artifactId> | 
				
			|||
                <version>3.2.2</version> | 
				
			|||
                <configuration> | 
				
			|||
                    <archive> | 
				
			|||
                        <manifestEntries> | 
				
			|||
                            <Automatic-Module-Name>com.seaweedfs.jdbc</Automatic-Module-Name> | 
				
			|||
                        </manifestEntries> | 
				
			|||
                    </archive> | 
				
			|||
                </configuration> | 
				
			|||
            </plugin> | 
				
			|||
 | 
				
			|||
            <plugin> | 
				
			|||
                <groupId>org.apache.maven.plugins</groupId> | 
				
			|||
                <artifactId>maven-source-plugin</artifactId> | 
				
			|||
                <version>3.2.1</version> | 
				
			|||
                <executions> | 
				
			|||
                    <execution> | 
				
			|||
                        <id>attach-sources</id> | 
				
			|||
                        <goals> | 
				
			|||
                            <goal>jar</goal> | 
				
			|||
                        </goals> | 
				
			|||
                    </execution> | 
				
			|||
                </executions> | 
				
			|||
            </plugin> | 
				
			|||
 | 
				
			|||
            <plugin> | 
				
			|||
                <groupId>org.apache.maven.plugins</groupId> | 
				
			|||
                <artifactId>maven-javadoc-plugin</artifactId> | 
				
			|||
                <version>3.4.1</version> | 
				
			|||
                <executions> | 
				
			|||
                    <execution> | 
				
			|||
                        <id>attach-javadocs</id> | 
				
			|||
                        <goals> | 
				
			|||
                            <goal>jar</goal> | 
				
			|||
                        </goals> | 
				
			|||
                    </execution> | 
				
			|||
                </executions> | 
				
			|||
                <configuration> | 
				
			|||
                    <source>8</source> | 
				
			|||
                    <target>8</target> | 
				
			|||
                    <doclint>none</doclint> | 
				
			|||
                </configuration> | 
				
			|||
            </plugin> | 
				
			|||
        </plugins> | 
				
			|||
    </build> | 
				
			|||
 | 
				
			|||
    <profiles> | 
				
			|||
        <profile> | 
				
			|||
            <id>release</id> | 
				
			|||
            <build> | 
				
			|||
                <plugins> | 
				
			|||
                    <plugin> | 
				
			|||
                        <groupId>org.apache.maven.plugins</groupId> | 
				
			|||
                        <artifactId>maven-gpg-plugin</artifactId> | 
				
			|||
                        <version>3.0.1</version> | 
				
			|||
                        <executions> | 
				
			|||
                            <execution> | 
				
			|||
                                <id>sign-artifacts</id> | 
				
			|||
                                <phase>verify</phase> | 
				
			|||
                                <goals> | 
				
			|||
                                    <goal>sign</goal> | 
				
			|||
                                </goals> | 
				
			|||
                            </execution> | 
				
			|||
                        </executions> | 
				
			|||
                    </plugin> | 
				
			|||
                </plugins> | 
				
			|||
            </build> | 
				
			|||
        </profile> | 
				
			|||
    </profiles> | 
				
			|||
</project> | 
				
			|||
@ -0,0 +1,497 @@ | 
				
			|||
package com.seaweedfs.jdbc; | 
				
			|||
 | 
				
			|||
import org.slf4j.Logger; | 
				
			|||
import org.slf4j.LoggerFactory; | 
				
			|||
 | 
				
			|||
import java.io.*; | 
				
			|||
import java.net.Socket; | 
				
			|||
import java.net.SocketTimeoutException; | 
				
			|||
import java.nio.ByteBuffer; | 
				
			|||
import java.sql.*; | 
				
			|||
import java.util.Map; | 
				
			|||
import java.util.Properties; | 
				
			|||
import java.util.concurrent.Executor; | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * JDBC Connection implementation for SeaweedFS | 
				
			|||
 */ | 
				
			|||
public class SeaweedFSConnection implements Connection { | 
				
			|||
 | 
				
			|||
    private static final Logger logger = LoggerFactory.getLogger(SeaweedFSConnection.class); | 
				
			|||
     | 
				
			|||
    // Protocol constants (must match server implementation) | 
				
			|||
    private static final byte JDBC_MSG_CONNECT = 0x01; | 
				
			|||
    private static final byte JDBC_MSG_DISCONNECT = 0x02; | 
				
			|||
    private static final byte JDBC_MSG_EXECUTE_QUERY = 0x03; | 
				
			|||
    private static final byte JDBC_MSG_EXECUTE_UPDATE = 0x04; | 
				
			|||
    private static final byte JDBC_MSG_GET_METADATA = 0x07; | 
				
			|||
    private static final byte JDBC_MSG_SET_AUTOCOMMIT = 0x08; | 
				
			|||
    private static final byte JDBC_MSG_COMMIT = 0x09; | 
				
			|||
    private static final byte JDBC_MSG_ROLLBACK = 0x0A; | 
				
			|||
     | 
				
			|||
    private static final byte JDBC_RESP_OK = 0x00; | 
				
			|||
    private static final byte JDBC_RESP_ERROR = 0x01; | 
				
			|||
    private static final byte JDBC_RESP_RESULT_SET = 0x02; | 
				
			|||
    private static final byte JDBC_RESP_UPDATE_COUNT = 0x03; | 
				
			|||
    private static final byte JDBC_RESP_METADATA = 0x04; | 
				
			|||
     | 
				
			|||
    private final SeaweedFSConnectionInfo connectionInfo; | 
				
			|||
    private Socket socket; | 
				
			|||
    private DataInputStream inputStream; | 
				
			|||
    private DataOutputStream outputStream; | 
				
			|||
    private boolean closed = false; | 
				
			|||
    private boolean autoCommit = true; | 
				
			|||
    private String catalog = null; | 
				
			|||
    private int transactionIsolation = Connection.TRANSACTION_NONE; | 
				
			|||
    private boolean readOnly = true; // SeaweedFS is read-only | 
				
			|||
     | 
				
			|||
    public SeaweedFSConnection(SeaweedFSConnectionInfo connectionInfo) throws SQLException { | 
				
			|||
        this.connectionInfo = connectionInfo; | 
				
			|||
        connect(); | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    private void connect() throws SQLException { | 
				
			|||
        try { | 
				
			|||
            logger.debug("Connecting to SeaweedFS at {}:{}", connectionInfo.getHost(), connectionInfo.getPort()); | 
				
			|||
             | 
				
			|||
            // Create socket connection | 
				
			|||
            socket = new Socket(); | 
				
			|||
            socket.connect(new java.net.InetSocketAddress(connectionInfo.getHost(), connectionInfo.getPort()),  | 
				
			|||
                          connectionInfo.getConnectTimeout()); | 
				
			|||
             | 
				
			|||
            if (connectionInfo.getSocketTimeout() > 0) { | 
				
			|||
                socket.setSoTimeout(connectionInfo.getSocketTimeout()); | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            // Create streams | 
				
			|||
            inputStream = new DataInputStream(new BufferedInputStream(socket.getInputStream())); | 
				
			|||
            outputStream = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())); | 
				
			|||
             | 
				
			|||
            // Send connection message | 
				
			|||
            sendMessage(JDBC_MSG_CONNECT, connectionInfo.getDatabase().getBytes()); | 
				
			|||
             | 
				
			|||
            // Read response | 
				
			|||
            Response response = readResponse(); | 
				
			|||
            if (response.type == JDBC_RESP_ERROR) { | 
				
			|||
                throw new SQLException("Failed to connect: " + new String(response.data)); | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            logger.info("Successfully connected to SeaweedFS: {}", connectionInfo.getConnectionString()); | 
				
			|||
             | 
				
			|||
        } catch (Exception e) { | 
				
			|||
            if (socket != null && !socket.isClosed()) { | 
				
			|||
                try { | 
				
			|||
                    socket.close(); | 
				
			|||
                } catch (IOException ignored) {} | 
				
			|||
            } | 
				
			|||
            throw new SQLException("Failed to connect to SeaweedFS: " + e.getMessage(), e); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public Statement createStatement() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return new SeaweedFSStatement(this); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public PreparedStatement prepareStatement(String sql) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return new SeaweedFSPreparedStatement(this, sql); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public CallableStatement prepareCall(String sql) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Callable statements are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String nativeSQL(String sql) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return sql; // No translation needed | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setAutoCommit(boolean autoCommit) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        if (this.autoCommit != autoCommit) { | 
				
			|||
            sendMessage(JDBC_MSG_SET_AUTOCOMMIT, new byte[]{(byte)(autoCommit ? 1 : 0)}); | 
				
			|||
            Response response = readResponse(); | 
				
			|||
            if (response.type == JDBC_RESP_ERROR) { | 
				
			|||
                throw new SQLException("Failed to set auto-commit: " + new String(response.data)); | 
				
			|||
            } | 
				
			|||
            this.autoCommit = autoCommit; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean getAutoCommit() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return autoCommit; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void commit() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        if (autoCommit) { | 
				
			|||
            throw new SQLException("Cannot commit when auto-commit is enabled"); | 
				
			|||
        } | 
				
			|||
        sendMessage(JDBC_MSG_COMMIT, new byte[0]); | 
				
			|||
        Response response = readResponse(); | 
				
			|||
        if (response.type == JDBC_RESP_ERROR) { | 
				
			|||
            throw new SQLException("Failed to commit: " + new String(response.data)); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void rollback() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        if (autoCommit) { | 
				
			|||
            throw new SQLException("Cannot rollback when auto-commit is enabled"); | 
				
			|||
        } | 
				
			|||
        sendMessage(JDBC_MSG_ROLLBACK, new byte[0]); | 
				
			|||
        Response response = readResponse(); | 
				
			|||
        if (response.type == JDBC_RESP_ERROR) { | 
				
			|||
            throw new SQLException("Failed to rollback: " + new String(response.data)); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void close() throws SQLException { | 
				
			|||
        if (!closed) { | 
				
			|||
            try { | 
				
			|||
                if (outputStream != null) { | 
				
			|||
                    sendMessage(JDBC_MSG_DISCONNECT, new byte[0]); | 
				
			|||
                    outputStream.close(); | 
				
			|||
                } | 
				
			|||
                if (inputStream != null) { | 
				
			|||
                    inputStream.close(); | 
				
			|||
                } | 
				
			|||
                if (socket != null && !socket.isClosed()) { | 
				
			|||
                    socket.close(); | 
				
			|||
                } | 
				
			|||
            } catch (Exception e) { | 
				
			|||
                logger.warn("Error closing connection: {}", e.getMessage()); | 
				
			|||
            } finally { | 
				
			|||
                closed = true; | 
				
			|||
                logger.debug("Connection closed"); | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isClosed() throws SQLException { | 
				
			|||
        return closed || (socket != null && socket.isClosed()); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public DatabaseMetaData getMetaData() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return new SeaweedFSDatabaseMetaData(this); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setReadOnly(boolean readOnly) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        // SeaweedFS is always read-only, so we ignore attempts to change this | 
				
			|||
        this.readOnly = true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isReadOnly() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return readOnly; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setCatalog(String catalog) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        this.catalog = catalog; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getCatalog() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return catalog != null ? catalog : connectionInfo.getDatabase(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setTransactionIsolation(int level) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        this.transactionIsolation = level; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getTransactionIsolation() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return transactionIsolation; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public SQLWarning getWarnings() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return null; // No warnings for now | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void clearWarnings() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        // No-op | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    // Methods not commonly used - basic implementations | 
				
			|||
     | 
				
			|||
    @Override | 
				
			|||
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { | 
				
			|||
        return createStatement(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { | 
				
			|||
        return prepareStatement(sql); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Callable statements are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public Map<String, Class<?>> getTypeMap() throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Type maps are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Type maps are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setHoldability(int holdability) throws SQLException { | 
				
			|||
        // No-op | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getHoldability() throws SQLException { | 
				
			|||
        return ResultSet.CLOSE_CURSORS_AT_COMMIT; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public Savepoint setSavepoint() throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Savepoints are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public Savepoint setSavepoint(String name) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Savepoints are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void rollback(Savepoint savepoint) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Savepoints are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void releaseSavepoint(Savepoint savepoint) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Savepoints are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { | 
				
			|||
        return createStatement(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { | 
				
			|||
        return prepareStatement(sql); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Callable statements are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { | 
				
			|||
        return prepareStatement(sql); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { | 
				
			|||
        return prepareStatement(sql); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { | 
				
			|||
        return prepareStatement(sql); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public Clob createClob() throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Clob creation is not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public Blob createBlob() throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Blob creation is not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public NClob createNClob() throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("NClob creation is not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public SQLXML createSQLXML() throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("SQLXML creation is not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isValid(int timeout) throws SQLException { | 
				
			|||
        return !closed && socket != null && !socket.isClosed(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setClientInfo(String name, String value) throws SQLClientInfoException { | 
				
			|||
        // No-op | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setClientInfo(Properties properties) throws SQLClientInfoException { | 
				
			|||
        // No-op | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getClientInfo(String name) throws SQLException { | 
				
			|||
        return null; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public Properties getClientInfo() throws SQLException { | 
				
			|||
        return new Properties(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Array creation is not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Struct creation is not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setSchema(String schema) throws SQLException { | 
				
			|||
        // No-op | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getSchema() throws SQLException { | 
				
			|||
        return connectionInfo.getDatabase(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void abort(Executor executor) throws SQLException { | 
				
			|||
        close(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { | 
				
			|||
        try { | 
				
			|||
            if (socket != null) { | 
				
			|||
                socket.setSoTimeout(milliseconds); | 
				
			|||
            } | 
				
			|||
        } catch (Exception e) { | 
				
			|||
            throw new SQLException("Failed to set network timeout", e); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getNetworkTimeout() throws SQLException { | 
				
			|||
        try { | 
				
			|||
            return socket != null ? socket.getSoTimeout() : 0; | 
				
			|||
        } catch (Exception e) { | 
				
			|||
            throw new SQLException("Failed to get network timeout", e); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public <T> T unwrap(Class<T> iface) throws SQLException { | 
				
			|||
        if (iface.isAssignableFrom(getClass())) { | 
				
			|||
            return iface.cast(this); | 
				
			|||
        } | 
				
			|||
        throw new SQLException("Cannot unwrap to " + iface.getName()); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isWrapperFor(Class<?> iface) throws SQLException { | 
				
			|||
        return iface.isAssignableFrom(getClass()); | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    // Package-private methods for use by Statement and other classes | 
				
			|||
     | 
				
			|||
    void sendMessage(byte messageType, byte[] data) throws SQLException { | 
				
			|||
        try { | 
				
			|||
            synchronized (outputStream) { | 
				
			|||
                // Write header: message type (1 byte) + data length (4 bytes) | 
				
			|||
                outputStream.writeByte(messageType); | 
				
			|||
                outputStream.writeInt(data.length); | 
				
			|||
                 | 
				
			|||
                // Write data | 
				
			|||
                if (data.length > 0) { | 
				
			|||
                    outputStream.write(data); | 
				
			|||
                } | 
				
			|||
                 | 
				
			|||
                outputStream.flush(); | 
				
			|||
            } | 
				
			|||
        } catch (IOException e) { | 
				
			|||
            throw new SQLException("Failed to send message to server", e); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    Response readResponse() throws SQLException { | 
				
			|||
        try { | 
				
			|||
            synchronized (inputStream) { | 
				
			|||
                // Read response type | 
				
			|||
                byte responseType = inputStream.readByte(); | 
				
			|||
                 | 
				
			|||
                // Read data length | 
				
			|||
                int dataLength = inputStream.readInt(); | 
				
			|||
                 | 
				
			|||
                // Read data | 
				
			|||
                byte[] data = new byte[dataLength]; | 
				
			|||
                if (dataLength > 0) { | 
				
			|||
                    inputStream.readFully(data); | 
				
			|||
                } | 
				
			|||
                 | 
				
			|||
                return new Response(responseType, data); | 
				
			|||
            } | 
				
			|||
        } catch (SocketTimeoutException e) { | 
				
			|||
            throw new SQLException("Read timeout from server", e); | 
				
			|||
        } catch (IOException e) { | 
				
			|||
            throw new SQLException("Failed to read response from server", e); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    private void checkClosed() throws SQLException { | 
				
			|||
        if (closed) { | 
				
			|||
            throw new SQLException("Connection is closed"); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    SeaweedFSConnectionInfo getConnectionInfo() { | 
				
			|||
        return connectionInfo; | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    // Helper class for responses | 
				
			|||
    static class Response { | 
				
			|||
        final byte type; | 
				
			|||
        final byte[] data; | 
				
			|||
         | 
				
			|||
        Response(byte type, byte[] data) { | 
				
			|||
            this.type = type; | 
				
			|||
            this.data = data; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,71 @@ | 
				
			|||
package com.seaweedfs.jdbc; | 
				
			|||
 | 
				
			|||
import java.util.Properties; | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * Connection information holder for SeaweedFS JDBC connections | 
				
			|||
 */ | 
				
			|||
public class SeaweedFSConnectionInfo { | 
				
			|||
     | 
				
			|||
    private final String host; | 
				
			|||
    private final int port; | 
				
			|||
    private final String database; | 
				
			|||
    private final String user; | 
				
			|||
    private final String password; | 
				
			|||
    private final int connectTimeout; | 
				
			|||
    private final int socketTimeout; | 
				
			|||
    private final Properties properties; | 
				
			|||
     | 
				
			|||
    public SeaweedFSConnectionInfo(Properties props) { | 
				
			|||
        this.properties = new Properties(props); | 
				
			|||
        this.host = props.getProperty(SeaweedFSDriver.PROP_HOST, "localhost"); | 
				
			|||
        this.port = Integer.parseInt(props.getProperty(SeaweedFSDriver.PROP_PORT, "8089")); | 
				
			|||
        this.database = props.getProperty(SeaweedFSDriver.PROP_DATABASE, "default"); | 
				
			|||
        this.user = props.getProperty(SeaweedFSDriver.PROP_USER, ""); | 
				
			|||
        this.password = props.getProperty(SeaweedFSDriver.PROP_PASSWORD, ""); | 
				
			|||
        this.connectTimeout = Integer.parseInt(props.getProperty(SeaweedFSDriver.PROP_CONNECT_TIMEOUT, "30000")); | 
				
			|||
        this.socketTimeout = Integer.parseInt(props.getProperty(SeaweedFSDriver.PROP_SOCKET_TIMEOUT, "0")); | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    public String getHost() { | 
				
			|||
        return host; | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    public int getPort() { | 
				
			|||
        return port; | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    public String getDatabase() { | 
				
			|||
        return database; | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    public String getUser() { | 
				
			|||
        return user; | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    public String getPassword() { | 
				
			|||
        return password; | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    public int getConnectTimeout() { | 
				
			|||
        return connectTimeout; | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    public int getSocketTimeout() { | 
				
			|||
        return socketTimeout; | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    public Properties getProperties() { | 
				
			|||
        return new Properties(properties); | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    public String getConnectionString() { | 
				
			|||
        return String.format("jdbc:seaweedfs://%s:%d/%s", host, port, database); | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    @Override | 
				
			|||
    public String toString() { | 
				
			|||
        return String.format("SeaweedFSConnectionInfo{host='%s', port=%d, database='%s', user='%s', connectTimeout=%d, socketTimeout=%d}", | 
				
			|||
                host, port, database, user, connectTimeout, socketTimeout); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,972 @@ | 
				
			|||
package com.seaweedfs.jdbc; | 
				
			|||
 | 
				
			|||
import java.sql.*; | 
				
			|||
import java.util.ArrayList; | 
				
			|||
import java.util.List; | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * DatabaseMetaData implementation for SeaweedFS JDBC | 
				
			|||
 */ | 
				
			|||
public class SeaweedFSDatabaseMetaData implements DatabaseMetaData { | 
				
			|||
     | 
				
			|||
    private final SeaweedFSConnection connection; | 
				
			|||
     | 
				
			|||
    public SeaweedFSDatabaseMetaData(SeaweedFSConnection connection) { | 
				
			|||
        this.connection = connection; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean allProceduresAreCallable() throws SQLException { | 
				
			|||
        return false; // No stored procedures | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean allTablesAreSelectable() throws SQLException { | 
				
			|||
        return true; // All tables are selectable | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getURL() throws SQLException { | 
				
			|||
        return connection.getConnectionInfo().getConnectionString(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getUserName() throws SQLException { | 
				
			|||
        return connection.getConnectionInfo().getUser(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isReadOnly() throws SQLException { | 
				
			|||
        return true; // SeaweedFS is read-only | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean nullsAreSortedHigh() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean nullsAreSortedLow() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean nullsAreSortedAtStart() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean nullsAreSortedAtEnd() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getDatabaseProductName() throws SQLException { | 
				
			|||
        return "SeaweedFS"; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getDatabaseProductVersion() throws SQLException { | 
				
			|||
        return "1.0.0"; // This could be retrieved from the server | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getDriverName() throws SQLException { | 
				
			|||
        return SeaweedFSDriver.DRIVER_NAME; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getDriverVersion() throws SQLException { | 
				
			|||
        return SeaweedFSDriver.DRIVER_VERSION; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getDriverMajorVersion() { | 
				
			|||
        return SeaweedFSDriver.DRIVER_MAJOR_VERSION; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getDriverMinorVersion() { | 
				
			|||
        return SeaweedFSDriver.DRIVER_MINOR_VERSION; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean usesLocalFiles() throws SQLException { | 
				
			|||
        return false; // SeaweedFS uses distributed storage | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean usesLocalFilePerTable() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsMixedCaseIdentifiers() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean storesUpperCaseIdentifiers() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean storesLowerCaseIdentifiers() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean storesMixedCaseIdentifiers() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean storesUpperCaseQuotedIdentifiers() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean storesLowerCaseQuotedIdentifiers() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean storesMixedCaseQuotedIdentifiers() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getIdentifierQuoteString() throws SQLException { | 
				
			|||
        return "`"; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getSQLKeywords() throws SQLException { | 
				
			|||
        return ""; // No additional keywords beyond SQL standard | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getNumericFunctions() throws SQLException { | 
				
			|||
        return "COUNT,SUM,AVG,MIN,MAX"; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getStringFunctions() throws SQLException { | 
				
			|||
        return ""; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getSystemFunctions() throws SQLException { | 
				
			|||
        return ""; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getTimeDateFunctions() throws SQLException { | 
				
			|||
        return ""; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getSearchStringEscape() throws SQLException { | 
				
			|||
        return "\\"; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getExtraNameCharacters() throws SQLException { | 
				
			|||
        return ""; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsAlterTableWithAddColumn() throws SQLException { | 
				
			|||
        return false; // No DDL support | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsAlterTableWithDropColumn() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsColumnAliasing() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean nullPlusNonNullIsNull() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsConvert() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsConvert(int fromType, int toType) throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsTableCorrelationNames() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsDifferentTableCorrelationNames() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsExpressionsInOrderBy() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsOrderByUnrelated() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsGroupBy() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsGroupByUnrelated() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsGroupByBeyondSelect() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsLikeEscapeClause() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsMultipleResultSets() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsMultipleTransactions() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsNonNullableColumns() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsMinimumSQLGrammar() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsCoreSQLGrammar() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsExtendedSQLGrammar() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsANSI92EntryLevelSQL() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsANSI92IntermediateSQL() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsANSI92FullSQL() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsIntegrityEnhancementFacility() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsOuterJoins() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsFullOuterJoins() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsLimitedOuterJoins() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getSchemaTerm() throws SQLException { | 
				
			|||
        return "schema"; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getProcedureTerm() throws SQLException { | 
				
			|||
        return "procedure"; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getCatalogTerm() throws SQLException { | 
				
			|||
        return "catalog"; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isCatalogAtStart() throws SQLException { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getCatalogSeparator() throws SQLException { | 
				
			|||
        return "."; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsSchemasInDataManipulation() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsSchemasInProcedureCalls() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsSchemasInTableDefinitions() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsSchemasInIndexDefinitions() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsCatalogsInDataManipulation() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsCatalogsInProcedureCalls() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsCatalogsInTableDefinitions() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsCatalogsInIndexDefinitions() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsPositionedDelete() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsPositionedUpdate() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsSelectForUpdate() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsStoredProcedures() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsSubqueriesInComparisons() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsSubqueriesInExists() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsSubqueriesInIns() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsSubqueriesInQuantifieds() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsCorrelatedSubqueries() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsUnion() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsUnionAll() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsOpenCursorsAcrossCommit() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsOpenCursorsAcrossRollback() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsOpenStatementsAcrossCommit() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsOpenStatementsAcrossRollback() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxBinaryLiteralLength() throws SQLException { | 
				
			|||
        return 0; // No limit | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxCharLiteralLength() throws SQLException { | 
				
			|||
        return 0; // No limit | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxColumnNameLength() throws SQLException { | 
				
			|||
        return 255; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxColumnsInGroupBy() throws SQLException { | 
				
			|||
        return 0; // No limit | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxColumnsInIndex() throws SQLException { | 
				
			|||
        return 0; // No indexes | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxColumnsInOrderBy() throws SQLException { | 
				
			|||
        return 0; // No limit | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxColumnsInSelect() throws SQLException { | 
				
			|||
        return 0; // No limit | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxColumnsInTable() throws SQLException { | 
				
			|||
        return 0; // No limit | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxConnections() throws SQLException { | 
				
			|||
        return 0; // No limit | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxCursorNameLength() throws SQLException { | 
				
			|||
        return 0; // No cursors | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxIndexLength() throws SQLException { | 
				
			|||
        return 0; // No indexes | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxSchemaNameLength() throws SQLException { | 
				
			|||
        return 255; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxProcedureNameLength() throws SQLException { | 
				
			|||
        return 0; // No procedures | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxCatalogNameLength() throws SQLException { | 
				
			|||
        return 255; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxRowSize() throws SQLException { | 
				
			|||
        return 0; // No limit | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean doesMaxRowSizeIncludeBlobs() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxStatementLength() throws SQLException { | 
				
			|||
        return 0; // No limit | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxStatements() throws SQLException { | 
				
			|||
        return 0; // No limit | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxTableNameLength() throws SQLException { | 
				
			|||
        return 255; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxTablesInSelect() throws SQLException { | 
				
			|||
        return 1; // Only single table selects supported | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxUserNameLength() throws SQLException { | 
				
			|||
        return 255; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getDefaultTransactionIsolation() throws SQLException { | 
				
			|||
        return Connection.TRANSACTION_NONE; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsTransactions() throws SQLException { | 
				
			|||
        return false; // No transactions | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsTransactionIsolationLevel(int level) throws SQLException { | 
				
			|||
        return level == Connection.TRANSACTION_NONE; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsDataManipulationTransactionsOnly() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean dataDefinitionCausesTransactionCommit() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean dataDefinitionIgnoredInTransactions() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException { | 
				
			|||
        // Return empty result set - no procedures | 
				
			|||
        return createEmptyResultSet(new String[]{"PROCEDURE_CAT", "PROCEDURE_SCHEM", "PROCEDURE_NAME", "reserved1", "reserved2", "reserved3", "REMARKS", "PROCEDURE_TYPE", "SPECIFIC_NAME"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException { | 
				
			|||
        // Return empty result set - no procedures | 
				
			|||
        return createEmptyResultSet(new String[]{"PROCEDURE_CAT", "PROCEDURE_SCHEM", "PROCEDURE_NAME", "COLUMN_NAME", "COLUMN_TYPE", "DATA_TYPE", "TYPE_NAME", "PRECISION", "LENGTH", "SCALE", "RADIX", "NULLABLE", "REMARKS", "COLUMN_DEF", "SQL_DATA_TYPE", "SQL_DATETIME_SUB", "CHAR_OCTET_LENGTH", "ORDINAL_POSITION", "IS_NULLABLE", "SPECIFIC_NAME"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) throws SQLException { | 
				
			|||
        // For now, return empty result set | 
				
			|||
        // In a full implementation, this would query the schema catalog | 
				
			|||
        return createEmptyResultSet(new String[]{"TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "TABLE_TYPE", "REMARKS", "TYPE_CAT", "TYPE_SCHEM", "TYPE_NAME", "SELF_REFERENCING_COL_NAME", "REF_GENERATION"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getSchemas() throws SQLException { | 
				
			|||
        return getSchemas(null, null); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getCatalogs() throws SQLException { | 
				
			|||
        // Return default catalog | 
				
			|||
        List<List<String>> rows = new ArrayList<>(); | 
				
			|||
        rows.add(List.of("default")); | 
				
			|||
        return createResultSet(new String[]{"TABLE_CAT"}, rows); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getTableTypes() throws SQLException { | 
				
			|||
        List<List<String>> rows = new ArrayList<>(); | 
				
			|||
        rows.add(List.of("TABLE")); | 
				
			|||
        return createResultSet(new String[]{"TABLE_TYPE"}, rows); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { | 
				
			|||
        // Return empty result set for now | 
				
			|||
        return createEmptyResultSet(new String[]{"TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "DATA_TYPE", "TYPE_NAME", "COLUMN_SIZE", "BUFFER_LENGTH", "DECIMAL_DIGITS", "NUM_PREC_RADIX", "NULLABLE", "REMARKS", "COLUMN_DEF", "SQL_DATA_TYPE", "SQL_DATETIME_SUB", "CHAR_OCTET_LENGTH", "ORDINAL_POSITION", "IS_NULLABLE", "SCOPE_CATALOG", "SCOPE_SCHEMA", "SCOPE_TABLE", "SOURCE_DATA_TYPE", "IS_AUTOINCREMENT", "IS_GENERATEDCOLUMN"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "GRANTOR", "GRANTEE", "PRIVILEGE", "IS_GRANTABLE"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "GRANTOR", "GRANTEE", "PRIVILEGE", "IS_GRANTABLE"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"SCOPE", "COLUMN_NAME", "DATA_TYPE", "TYPE_NAME", "COLUMN_SIZE", "BUFFER_LENGTH", "DECIMAL_DIGITS", "PSEUDO_COLUMN"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"SCOPE", "COLUMN_NAME", "DATA_TYPE", "TYPE_NAME", "COLUMN_SIZE", "BUFFER_LENGTH", "DECIMAL_DIGITS", "PSEUDO_COLUMN"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "KEY_SEQ", "PK_NAME"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"PKTABLE_CAT", "PKTABLE_SCHEM", "PKTABLE_NAME", "PKCOLUMN_NAME", "FKTABLE_CAT", "FKTABLE_SCHEM", "FKTABLE_NAME", "FKCOLUMN_NAME", "KEY_SEQ", "UPDATE_RULE", "DELETE_RULE", "FK_NAME", "PK_NAME", "DEFERRABILITY"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"PKTABLE_CAT", "PKTABLE_SCHEM", "PKTABLE_NAME", "PKCOLUMN_NAME", "FKTABLE_CAT", "FKTABLE_SCHEM", "FKTABLE_NAME", "FKCOLUMN_NAME", "KEY_SEQ", "UPDATE_RULE", "DELETE_RULE", "FK_NAME", "PK_NAME", "DEFERRABILITY"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"PKTABLE_CAT", "PKTABLE_SCHEM", "PKTABLE_NAME", "PKCOLUMN_NAME", "FKTABLE_CAT", "FKTABLE_SCHEM", "FKTABLE_NAME", "FKCOLUMN_NAME", "KEY_SEQ", "UPDATE_RULE", "DELETE_RULE", "FK_NAME", "PK_NAME", "DEFERRABILITY"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getTypeInfo() throws SQLException { | 
				
			|||
        List<List<String>> rows = new ArrayList<>(); | 
				
			|||
        // Add basic SQL types | 
				
			|||
        rows.add(List.of("VARCHAR", String.valueOf(Types.VARCHAR), "65535", "'", "'", "length", "1", "3", "1", "0", "0", "0", "VARCHAR", "0", "0", String.valueOf(Types.VARCHAR), "0", "10")); | 
				
			|||
        rows.add(List.of("BIGINT", String.valueOf(Types.BIGINT), "19", null, null, null, "1", "2", "0", "0", "0", "1", "BIGINT", "0", "0", String.valueOf(Types.BIGINT), "0", "10")); | 
				
			|||
        rows.add(List.of("BOOLEAN", String.valueOf(Types.BOOLEAN), "1", null, null, null, "1", "2", "0", "0", "0", "1", "BOOLEAN", "0", "0", String.valueOf(Types.BOOLEAN), "0", "10")); | 
				
			|||
        rows.add(List.of("TIMESTAMP", String.valueOf(Types.TIMESTAMP), "23", "'", "'", null, "1", "3", "0", "0", "0", "0", "TIMESTAMP", "0", "6", String.valueOf(Types.TIMESTAMP), "0", "10")); | 
				
			|||
         | 
				
			|||
        return createResultSet(new String[]{"TYPE_NAME", "DATA_TYPE", "PRECISION", "LITERAL_PREFIX", "LITERAL_SUFFIX", "CREATE_PARAMS", "NULLABLE", "CASE_SENSITIVE", "SEARCHABLE", "UNSIGNED_ATTRIBUTE", "FIXED_PREC_SCALE", "AUTO_INCREMENT", "LOCAL_TYPE_NAME", "MINIMUM_SCALE", "MAXIMUM_SCALE", "SQL_DATA_TYPE", "SQL_DATETIME_SUB", "NUM_PREC_RADIX"}, rows); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "NON_UNIQUE", "INDEX_QUALIFIER", "INDEX_NAME", "TYPE", "ORDINAL_POSITION", "COLUMN_NAME", "ASC_OR_DESC", "CARDINALITY", "PAGES", "FILTER_CONDITION"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsResultSetType(int type) throws SQLException { | 
				
			|||
        return type == ResultSet.TYPE_FORWARD_ONLY; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException { | 
				
			|||
        return type == ResultSet.TYPE_FORWARD_ONLY && concurrency == ResultSet.CONCUR_READ_ONLY; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean ownUpdatesAreVisible(int type) throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean ownDeletesAreVisible(int type) throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean ownInsertsAreVisible(int type) throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean othersUpdatesAreVisible(int type) throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean othersDeletesAreVisible(int type) throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean othersInsertsAreVisible(int type) throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean updatesAreDetected(int type) throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean deletesAreDetected(int type) throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean insertsAreDetected(int type) throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsBatchUpdates() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"TYPE_CAT", "TYPE_SCHEM", "TYPE_NAME", "CLASS_NAME", "DATA_TYPE", "REMARKS", "BASE_TYPE"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public Connection getConnection() throws SQLException { | 
				
			|||
        return connection; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    // JDBC 3.0 methods | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsSavepoints() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsNamedParameters() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsMultipleOpenResults() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsGetGeneratedKeys() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"TYPE_CAT", "TYPE_SCHEM", "TYPE_NAME", "SUPERTYPE_CAT", "SUPERTYPE_SCHEM", "SUPERTYPE_NAME"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "SUPERTABLE_NAME"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"TYPE_CAT", "TYPE_SCHEM", "TYPE_NAME", "ATTR_NAME", "DATA_TYPE", "ATTR_TYPE_NAME", "ATTR_SIZE", "DECIMAL_DIGITS", "NUM_PREC_RADIX", "NULLABLE", "REMARKS", "ATTR_DEF", "SQL_DATA_TYPE", "SQL_DATETIME_SUB", "CHAR_OCTET_LENGTH", "ORDINAL_POSITION", "IS_NULLABLE", "SCOPE_CATALOG", "SCOPE_SCHEMA", "SCOPE_TABLE", "SOURCE_DATA_TYPE"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsResultSetHoldability(int holdability) throws SQLException { | 
				
			|||
        return holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getResultSetHoldability() throws SQLException { | 
				
			|||
        return ResultSet.CLOSE_CURSORS_AT_COMMIT; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getDatabaseMajorVersion() throws SQLException { | 
				
			|||
        return 1; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getDatabaseMinorVersion() throws SQLException { | 
				
			|||
        return 0; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getJDBCMajorVersion() throws SQLException { | 
				
			|||
        return 4; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getJDBCMinorVersion() throws SQLException { | 
				
			|||
        return 0; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getSQLStateType() throws SQLException { | 
				
			|||
        return DatabaseMetaData.sqlStateSQL; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean locatorsUpdateCopy() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsStatementPooling() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    // JDBC 4.0 methods | 
				
			|||
    @Override | 
				
			|||
    public RowIdLifetime getRowIdLifetime() throws SQLException { | 
				
			|||
        return RowIdLifetime.ROWID_UNSUPPORTED; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException { | 
				
			|||
        List<List<String>> rows = new ArrayList<>(); | 
				
			|||
        rows.add(List.of(connection.getConnectionInfo().getDatabase(), "default")); | 
				
			|||
        return createResultSet(new String[]{"TABLE_SCHEM", "TABLE_CATALOG"}, rows); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean autoCommitFailureClosesAllResultSets() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getClientInfoProperties() throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"NAME", "MAX_LEN", "DEFAULT_VALUE", "DESCRIPTION"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"FUNCTION_CAT", "FUNCTION_SCHEM", "FUNCTION_NAME", "REMARKS", "FUNCTION_TYPE", "SPECIFIC_NAME"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"FUNCTION_CAT", "FUNCTION_SCHEM", "FUNCTION_NAME", "COLUMN_NAME", "COLUMN_TYPE", "DATA_TYPE", "TYPE_NAME", "PRECISION", "LENGTH", "SCALE", "RADIX", "NULLABLE", "REMARKS", "CHAR_OCTET_LENGTH", "ORDINAL_POSITION", "IS_NULLABLE", "SPECIFIC_NAME"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    // JDBC 4.1 methods | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { | 
				
			|||
        return createEmptyResultSet(new String[]{"TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "DATA_TYPE", "COLUMN_SIZE", "DECIMAL_DIGITS", "NUM_PREC_RADIX", "COLUMN_USAGE", "REMARKS", "CHAR_OCTET_LENGTH", "IS_NULLABLE"}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean generatedKeyAlwaysReturned() throws SQLException { | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public <T> T unwrap(Class<T> iface) throws SQLException { | 
				
			|||
        if (iface.isAssignableFrom(getClass())) { | 
				
			|||
            return iface.cast(this); | 
				
			|||
        } | 
				
			|||
        throw new SQLException("Cannot unwrap to " + iface.getName()); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isWrapperFor(Class<?> iface) throws SQLException { | 
				
			|||
        return iface.isAssignableFrom(getClass()); | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    // Helper methods to create result sets | 
				
			|||
    private ResultSet createEmptyResultSet(String[] columnNames) throws SQLException { | 
				
			|||
        return createResultSet(columnNames, new ArrayList<>()); | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    private ResultSet createResultSet(String[] columnNames, List<List<String>> rows) throws SQLException { | 
				
			|||
        // Convert to the format expected by SeaweedFSResultSet | 
				
			|||
        byte[] data = serializeResultSetData(columnNames, rows); | 
				
			|||
        return new SeaweedFSResultSet(null, data); | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    private byte[] serializeResultSetData(String[] columnNames, List<List<String>> rows) { | 
				
			|||
        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); | 
				
			|||
        java.io.DataOutputStream dos = new java.io.DataOutputStream(baos); | 
				
			|||
         | 
				
			|||
        try { | 
				
			|||
            // Column count | 
				
			|||
            dos.writeInt(columnNames.length); | 
				
			|||
             | 
				
			|||
            // Column names | 
				
			|||
            for (String name : columnNames) { | 
				
			|||
                byte[] nameBytes = name.getBytes(); | 
				
			|||
                dos.writeInt(nameBytes.length); | 
				
			|||
                dos.write(nameBytes); | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            // Row count | 
				
			|||
            dos.writeInt(rows.size()); | 
				
			|||
             | 
				
			|||
            // Rows | 
				
			|||
            for (List<String> row : rows) { | 
				
			|||
                for (String value : row) { | 
				
			|||
                    if (value != null) { | 
				
			|||
                        byte[] valueBytes = value.getBytes(); | 
				
			|||
                        dos.writeInt(valueBytes.length); | 
				
			|||
                        dos.write(valueBytes); | 
				
			|||
                    } else { | 
				
			|||
                        dos.writeInt(0); // null value | 
				
			|||
                    } | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            dos.flush(); | 
				
			|||
            return baos.toByteArray(); | 
				
			|||
             | 
				
			|||
        } catch (Exception e) { | 
				
			|||
            throw new RuntimeException("Failed to serialize result set data", e); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,207 @@ | 
				
			|||
package com.seaweedfs.jdbc; | 
				
			|||
 | 
				
			|||
import org.slf4j.Logger; | 
				
			|||
import org.slf4j.LoggerFactory; | 
				
			|||
 | 
				
			|||
import java.sql.*; | 
				
			|||
import java.util.Properties; | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * SeaweedFS JDBC Driver | 
				
			|||
 *  | 
				
			|||
 * Provides JDBC connectivity to SeaweedFS SQL engine for querying MQ topics. | 
				
			|||
 *  | 
				
			|||
 * JDBC URL format: jdbc:seaweedfs://host:port/database | 
				
			|||
 *  | 
				
			|||
 * Example usage: | 
				
			|||
 * <pre> | 
				
			|||
 * Class.forName("com.seaweedfs.jdbc.SeaweedFSDriver"); | 
				
			|||
 * Connection conn = DriverManager.getConnection("jdbc:seaweedfs://localhost:8089/default"); | 
				
			|||
 * Statement stmt = conn.createStatement(); | 
				
			|||
 * ResultSet rs = stmt.executeQuery("SELECT * FROM my_topic LIMIT 10"); | 
				
			|||
 * </pre> | 
				
			|||
 */ | 
				
			|||
public class SeaweedFSDriver implements Driver { | 
				
			|||
 | 
				
			|||
    private static final Logger logger = LoggerFactory.getLogger(SeaweedFSDriver.class); | 
				
			|||
     | 
				
			|||
    // Driver information | 
				
			|||
    public static final String DRIVER_NAME = "SeaweedFS JDBC Driver"; | 
				
			|||
    public static final String DRIVER_VERSION = "1.0.0"; | 
				
			|||
    public static final int DRIVER_MAJOR_VERSION = 1; | 
				
			|||
    public static final int DRIVER_MINOR_VERSION = 0; | 
				
			|||
     | 
				
			|||
    // URL prefix for SeaweedFS JDBC connections | 
				
			|||
    public static final String URL_PREFIX = "jdbc:seaweedfs://"; | 
				
			|||
     | 
				
			|||
    // Default connection properties | 
				
			|||
    public static final String PROP_HOST = "host"; | 
				
			|||
    public static final String PROP_PORT = "port"; | 
				
			|||
    public static final String PROP_DATABASE = "database"; | 
				
			|||
    public static final String PROP_USER = "user"; | 
				
			|||
    public static final String PROP_PASSWORD = "password"; | 
				
			|||
    public static final String PROP_CONNECT_TIMEOUT = "connectTimeout"; | 
				
			|||
    public static final String PROP_SOCKET_TIMEOUT = "socketTimeout"; | 
				
			|||
     | 
				
			|||
    static { | 
				
			|||
        try { | 
				
			|||
            // Register the driver with the DriverManager | 
				
			|||
            DriverManager.registerDriver(new SeaweedFSDriver()); | 
				
			|||
            logger.info("SeaweedFS JDBC Driver {} registered successfully", DRIVER_VERSION); | 
				
			|||
        } catch (SQLException e) { | 
				
			|||
            logger.error("Failed to register SeaweedFS JDBC Driver", e); | 
				
			|||
            throw new RuntimeException("Failed to register SeaweedFS JDBC Driver", e); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public Connection connect(String url, Properties info) throws SQLException { | 
				
			|||
        if (!acceptsURL(url)) { | 
				
			|||
            return null; // Not our URL, let another driver handle it | 
				
			|||
        } | 
				
			|||
         | 
				
			|||
        logger.debug("Attempting to connect to: {}", url); | 
				
			|||
         | 
				
			|||
        try { | 
				
			|||
            // Parse the URL to extract connection parameters | 
				
			|||
            SeaweedFSConnectionInfo connectionInfo = parseURL(url, info); | 
				
			|||
             | 
				
			|||
            // Create and return the connection | 
				
			|||
            return new SeaweedFSConnection(connectionInfo); | 
				
			|||
             | 
				
			|||
        } catch (Exception e) { | 
				
			|||
            logger.error("Failed to connect to SeaweedFS: {}", e.getMessage(), e); | 
				
			|||
            throw new SQLException("Failed to connect to SeaweedFS: " + e.getMessage(), e); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean acceptsURL(String url) throws SQLException { | 
				
			|||
        return url != null && url.startsWith(URL_PREFIX); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { | 
				
			|||
        return new DriverPropertyInfo[] { | 
				
			|||
            createPropertyInfo(PROP_HOST, "localhost", "SeaweedFS JDBC server hostname", null, false), | 
				
			|||
            createPropertyInfo(PROP_PORT, "8089", "SeaweedFS JDBC server port", null, false), | 
				
			|||
            createPropertyInfo(PROP_DATABASE, "default", "Database/namespace name", null, false), | 
				
			|||
            createPropertyInfo(PROP_USER, "", "Username (optional)", null, false), | 
				
			|||
            createPropertyInfo(PROP_PASSWORD, "", "Password (optional)", null, false), | 
				
			|||
            createPropertyInfo(PROP_CONNECT_TIMEOUT, "30000", "Connection timeout in milliseconds", null, false), | 
				
			|||
            createPropertyInfo(PROP_SOCKET_TIMEOUT, "0", "Socket timeout in milliseconds (0 = infinite)", null, false) | 
				
			|||
        }; | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    private DriverPropertyInfo createPropertyInfo(String name, String defaultValue, String description, String[] choices, boolean required) { | 
				
			|||
        DriverPropertyInfo info = new DriverPropertyInfo(name, defaultValue); | 
				
			|||
        info.description = description; | 
				
			|||
        info.choices = choices; | 
				
			|||
        info.required = required; | 
				
			|||
        return info; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMajorVersion() { | 
				
			|||
        return DRIVER_MAJOR_VERSION; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMinorVersion() { | 
				
			|||
        return DRIVER_MINOR_VERSION; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean jdbcCompliant() { | 
				
			|||
        // We implement a subset of JDBC, so we're not fully compliant | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("getParentLogger is not supported"); | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    /** | 
				
			|||
     * Parse JDBC URL and extract connection information | 
				
			|||
     *  | 
				
			|||
     * Expected format: jdbc:seaweedfs://host:port/database[?property=value&...] | 
				
			|||
     */ | 
				
			|||
    private SeaweedFSConnectionInfo parseURL(String url, Properties info) throws SQLException { | 
				
			|||
        if (!acceptsURL(url)) { | 
				
			|||
            throw new SQLException("Invalid SeaweedFS JDBC URL: " + url); | 
				
			|||
        } | 
				
			|||
         | 
				
			|||
        try { | 
				
			|||
            // Remove the jdbc:seaweedfs:// prefix | 
				
			|||
            String remaining = url.substring(URL_PREFIX.length()); | 
				
			|||
             | 
				
			|||
            // Split into host:port/database and query parameters | 
				
			|||
            String[] parts = remaining.split("\\?", 2); | 
				
			|||
            String hostPortDb = parts[0]; | 
				
			|||
            String queryParams = parts.length > 1 ? parts[1] : ""; | 
				
			|||
             | 
				
			|||
            // Parse host, port, and database | 
				
			|||
            String host = "localhost"; | 
				
			|||
            int port = 8089; | 
				
			|||
            String database = "default"; | 
				
			|||
             | 
				
			|||
            if (hostPortDb.contains("/")) { | 
				
			|||
                String[] hostPortDbParts = hostPortDb.split("/", 2); | 
				
			|||
                String hostPort = hostPortDbParts[0]; | 
				
			|||
                database = hostPortDbParts[1]; | 
				
			|||
                 | 
				
			|||
                if (hostPort.contains(":")) { | 
				
			|||
                    String[] hostPortParts = hostPort.split(":", 2); | 
				
			|||
                    host = hostPortParts[0]; | 
				
			|||
                    port = Integer.parseInt(hostPortParts[1]); | 
				
			|||
                } else { | 
				
			|||
                    host = hostPort; | 
				
			|||
                } | 
				
			|||
            } else if (hostPortDb.contains(":")) { | 
				
			|||
                String[] hostPortParts = hostPortDb.split(":", 2); | 
				
			|||
                host = hostPortParts[0]; | 
				
			|||
                port = Integer.parseInt(hostPortParts[1]); | 
				
			|||
            } else if (!hostPortDb.isEmpty()) { | 
				
			|||
                host = hostPortDb; | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            // Create properties with defaults | 
				
			|||
            Properties connectionProps = new Properties(); | 
				
			|||
            connectionProps.setProperty(PROP_HOST, host); | 
				
			|||
            connectionProps.setProperty(PROP_PORT, String.valueOf(port)); | 
				
			|||
            connectionProps.setProperty(PROP_DATABASE, database); | 
				
			|||
            connectionProps.setProperty(PROP_USER, ""); | 
				
			|||
            connectionProps.setProperty(PROP_PASSWORD, ""); | 
				
			|||
            connectionProps.setProperty(PROP_CONNECT_TIMEOUT, "30000"); | 
				
			|||
            connectionProps.setProperty(PROP_SOCKET_TIMEOUT, "0"); | 
				
			|||
             | 
				
			|||
            // Override with provided properties | 
				
			|||
            if (info != null) { | 
				
			|||
                connectionProps.putAll(info); | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            // Parse query parameters | 
				
			|||
            if (!queryParams.isEmpty()) { | 
				
			|||
                for (String param : queryParams.split("&")) { | 
				
			|||
                    String[] keyValue = param.split("=", 2); | 
				
			|||
                    if (keyValue.length == 2) { | 
				
			|||
                        connectionProps.setProperty(keyValue[0], keyValue[1]); | 
				
			|||
                    } | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            return new SeaweedFSConnectionInfo(connectionProps); | 
				
			|||
             | 
				
			|||
        } catch (Exception e) { | 
				
			|||
            throw new SQLException("Failed to parse SeaweedFS JDBC URL: " + url, e); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    /** | 
				
			|||
     * Get driver information string | 
				
			|||
     */ | 
				
			|||
    public static String getDriverInfo() { | 
				
			|||
        return String.format("%s v%s", DRIVER_NAME, DRIVER_VERSION); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,352 @@ | 
				
			|||
package com.seaweedfs.jdbc; | 
				
			|||
 | 
				
			|||
import java.io.InputStream; | 
				
			|||
import java.io.Reader; | 
				
			|||
import java.math.BigDecimal; | 
				
			|||
import java.net.URL; | 
				
			|||
import java.sql.*; | 
				
			|||
import java.util.Calendar; | 
				
			|||
import java.util.HashMap; | 
				
			|||
import java.util.Map; | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * PreparedStatement implementation for SeaweedFS JDBC | 
				
			|||
 */ | 
				
			|||
public class SeaweedFSPreparedStatement extends SeaweedFSStatement implements PreparedStatement { | 
				
			|||
     | 
				
			|||
    private final String originalSql; | 
				
			|||
    private final Map<Integer, Object> parameters = new HashMap<>(); | 
				
			|||
     | 
				
			|||
    public SeaweedFSPreparedStatement(SeaweedFSConnection connection, String sql) { | 
				
			|||
        super(connection); | 
				
			|||
        this.originalSql = sql; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet executeQuery() throws SQLException { | 
				
			|||
        return executeQuery(buildSqlWithParameters()); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int executeUpdate() throws SQLException { | 
				
			|||
        return executeUpdate(buildSqlWithParameters()); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setNull(int parameterIndex, int sqlType) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, null); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setBoolean(int parameterIndex, boolean x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setByte(int parameterIndex, byte x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setShort(int parameterIndex, short x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setInt(int parameterIndex, int x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setLong(int parameterIndex, long x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setFloat(int parameterIndex, float x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setDouble(int parameterIndex, double x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setString(int parameterIndex, String x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setBytes(int parameterIndex, byte[] x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setDate(int parameterIndex, Date x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setTime(int parameterIndex, Time x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("ASCII streams are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Unicode streams are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Binary streams are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void clearParameters() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.clear(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { | 
				
			|||
        setObject(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setObject(int parameterIndex, Object x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean execute() throws SQLException { | 
				
			|||
        return execute(buildSqlWithParameters()); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void addBatch() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        addBatch(buildSqlWithParameters()); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Character streams are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setRef(int parameterIndex, Ref x) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Ref objects are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setBlob(int parameterIndex, Blob x) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Blob objects are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setClob(int parameterIndex, Clob x) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Clob objects are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setArray(int parameterIndex, Array x) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Array objects are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSetMetaData getMetaData() throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Prepared statement metadata is not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { | 
				
			|||
        setDate(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { | 
				
			|||
        setTime(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { | 
				
			|||
        setTimestamp(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { | 
				
			|||
        setNull(parameterIndex, sqlType); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setURL(int parameterIndex, URL x) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        parameters.put(parameterIndex, x.toString()); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ParameterMetaData getParameterMetaData() throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Parameter metadata is not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setRowId(int parameterIndex, RowId x) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("RowId objects are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setNString(int parameterIndex, String value) throws SQLException { | 
				
			|||
        setString(parameterIndex, value); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("NCharacter streams are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setNClob(int parameterIndex, NClob value) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("NClob objects are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Clob objects are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Blob objects are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("NClob objects are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("SQLXML objects are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { | 
				
			|||
        setObject(parameterIndex, x); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("ASCII streams are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Binary streams are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Character streams are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("ASCII streams are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Binary streams are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Character streams are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("NCharacter streams are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setClob(int parameterIndex, Reader reader) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Clob objects are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Blob objects are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setNClob(int parameterIndex, Reader reader) throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("NClob objects are not supported"); | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    /** | 
				
			|||
     * Build the final SQL string by replacing parameter placeholders with actual values | 
				
			|||
     */ | 
				
			|||
    private String buildSqlWithParameters() throws SQLException { | 
				
			|||
        String sql = originalSql; | 
				
			|||
         | 
				
			|||
        // Simple parameter substitution (not SQL-injection safe, but good enough for demo) | 
				
			|||
        // In a production implementation, you would use proper parameter binding | 
				
			|||
        for (Map.Entry<Integer, Object> entry : parameters.entrySet()) { | 
				
			|||
            String placeholder = "\\?"; // Find first ? placeholder | 
				
			|||
            String replacement; | 
				
			|||
             | 
				
			|||
            Object value = entry.getValue(); | 
				
			|||
            if (value == null) { | 
				
			|||
                replacement = "NULL"; | 
				
			|||
            } else if (value instanceof String) { | 
				
			|||
                // Escape single quotes and wrap in quotes | 
				
			|||
                replacement = "'" + value.toString().replace("'", "''") + "'"; | 
				
			|||
            } else if (value instanceof Number || value instanceof Boolean) { | 
				
			|||
                replacement = value.toString(); | 
				
			|||
            } else if (value instanceof Date) { | 
				
			|||
                replacement = "'" + value.toString() + "'"; | 
				
			|||
            } else if (value instanceof Timestamp) { | 
				
			|||
                replacement = "'" + value.toString() + "'"; | 
				
			|||
            } else { | 
				
			|||
                replacement = "'" + value.toString().replace("'", "''") + "'"; | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            // Replace the first occurrence of ? | 
				
			|||
            sql = sql.replaceFirst(placeholder, replacement); | 
				
			|||
        } | 
				
			|||
         | 
				
			|||
        return sql; | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
						
							
						
						
							1245
	
						
						jdbc-driver/src/main/java/com/seaweedfs/jdbc/SeaweedFSResultSet.java
						
							File diff suppressed because it is too large
							
							
								
									View File
								
							
						
					
				File diff suppressed because it is too large
							
							
								
									View File
								
							
						@ -0,0 +1,202 @@ | 
				
			|||
package com.seaweedfs.jdbc; | 
				
			|||
 | 
				
			|||
import java.sql.ResultSetMetaData; | 
				
			|||
import java.sql.SQLException; | 
				
			|||
import java.sql.Types; | 
				
			|||
import java.util.List; | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * ResultSetMetaData implementation for SeaweedFS JDBC | 
				
			|||
 */ | 
				
			|||
public class SeaweedFSResultSetMetaData implements ResultSetMetaData { | 
				
			|||
     | 
				
			|||
    private final List<String> columnNames; | 
				
			|||
     | 
				
			|||
    public SeaweedFSResultSetMetaData(List<String> columnNames) { | 
				
			|||
        this.columnNames = columnNames; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getColumnCount() throws SQLException { | 
				
			|||
        return columnNames.size(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isAutoIncrement(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return false; // SeaweedFS doesn't have auto-increment columns | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isCaseSensitive(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return true; // Assume case sensitive | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isSearchable(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return true; // All columns are searchable | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isCurrency(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return false; // No currency columns | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int isNullable(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return columnNullable; // Assume nullable | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isSigned(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        // For simplicity, assume all numeric types are signed | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getColumnDisplaySize(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return 50; // Default display size | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getColumnLabel(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return columnNames.get(column - 1); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getColumnName(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return columnNames.get(column - 1); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getSchemaName(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return ""; // No schema concept in SeaweedFS | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getPrecision(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return 0; // Unknown precision | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getScale(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return 0; // Unknown scale | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getTableName(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return ""; // Table name not available in result set metadata | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getCatalogName(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return ""; // No catalog concept in SeaweedFS | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getColumnType(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        // For simplicity, we'll determine type based on column name patterns | 
				
			|||
        String columnName = columnNames.get(column - 1).toLowerCase(); | 
				
			|||
         | 
				
			|||
        if (columnName.contains("timestamp") || columnName.contains("time") || columnName.equals("_timestamp_ns")) { | 
				
			|||
            return Types.TIMESTAMP; | 
				
			|||
        } else if (columnName.contains("id") || columnName.contains("count") || columnName.contains("size")) { | 
				
			|||
            return Types.BIGINT; | 
				
			|||
        } else if (columnName.contains("amount") || columnName.contains("price") || columnName.contains("rate")) { | 
				
			|||
            return Types.DECIMAL; | 
				
			|||
        } else if (columnName.contains("flag") || columnName.contains("enabled") || columnName.contains("active")) { | 
				
			|||
            return Types.BOOLEAN; | 
				
			|||
        } else { | 
				
			|||
            return Types.VARCHAR; // Default to VARCHAR | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getColumnTypeName(int column) throws SQLException { | 
				
			|||
        int sqlType = getColumnType(column); | 
				
			|||
        switch (sqlType) { | 
				
			|||
            case Types.VARCHAR: | 
				
			|||
                return "VARCHAR"; | 
				
			|||
            case Types.BIGINT: | 
				
			|||
                return "BIGINT"; | 
				
			|||
            case Types.DECIMAL: | 
				
			|||
                return "DECIMAL"; | 
				
			|||
            case Types.BOOLEAN: | 
				
			|||
                return "BOOLEAN"; | 
				
			|||
            case Types.TIMESTAMP: | 
				
			|||
                return "TIMESTAMP"; | 
				
			|||
            default: | 
				
			|||
                return "VARCHAR"; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isReadOnly(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return true; // SeaweedFS is read-only | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isWritable(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return false; // SeaweedFS is read-only | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isDefinitelyWritable(int column) throws SQLException { | 
				
			|||
        checkColumnIndex(column); | 
				
			|||
        return false; // SeaweedFS is read-only | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public String getColumnClassName(int column) throws SQLException { | 
				
			|||
        int sqlType = getColumnType(column); | 
				
			|||
        switch (sqlType) { | 
				
			|||
            case Types.VARCHAR: | 
				
			|||
                return "java.lang.String"; | 
				
			|||
            case Types.BIGINT: | 
				
			|||
                return "java.lang.Long"; | 
				
			|||
            case Types.DECIMAL: | 
				
			|||
                return "java.math.BigDecimal"; | 
				
			|||
            case Types.BOOLEAN: | 
				
			|||
                return "java.lang.Boolean"; | 
				
			|||
            case Types.TIMESTAMP: | 
				
			|||
                return "java.sql.Timestamp"; | 
				
			|||
            default: | 
				
			|||
                return "java.lang.String"; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public <T> T unwrap(Class<T> iface) throws SQLException { | 
				
			|||
        if (iface.isAssignableFrom(getClass())) { | 
				
			|||
            return iface.cast(this); | 
				
			|||
        } | 
				
			|||
        throw new SQLException("Cannot unwrap to " + iface.getName()); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isWrapperFor(Class<?> iface) throws SQLException { | 
				
			|||
        return iface.isAssignableFrom(getClass()); | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    private void checkColumnIndex(int column) throws SQLException { | 
				
			|||
        if (column < 1 || column > columnNames.size()) { | 
				
			|||
            throw new SQLException("Column index " + column + " is out of range (1-" + columnNames.size() + ")"); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,389 @@ | 
				
			|||
package com.seaweedfs.jdbc; | 
				
			|||
 | 
				
			|||
import org.slf4j.Logger; | 
				
			|||
import org.slf4j.LoggerFactory; | 
				
			|||
 | 
				
			|||
import java.sql.*; | 
				
			|||
import java.util.ArrayList; | 
				
			|||
import java.util.List; | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * JDBC Statement implementation for SeaweedFS | 
				
			|||
 */ | 
				
			|||
public class SeaweedFSStatement implements Statement { | 
				
			|||
     | 
				
			|||
    private static final Logger logger = LoggerFactory.getLogger(SeaweedFSStatement.class); | 
				
			|||
     | 
				
			|||
    protected final SeaweedFSConnection connection; | 
				
			|||
    private boolean closed = false; | 
				
			|||
    private ResultSet currentResultSet = null; | 
				
			|||
    private int updateCount = -1; | 
				
			|||
    private int maxRows = 0; | 
				
			|||
    private int queryTimeout = 0; | 
				
			|||
    private int fetchSize = 1000; | 
				
			|||
    private List<String> batch = new ArrayList<>(); | 
				
			|||
     | 
				
			|||
    public SeaweedFSStatement(SeaweedFSConnection connection) { | 
				
			|||
        this.connection = connection; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet executeQuery(String sql) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        logger.debug("Executing query: {}", sql); | 
				
			|||
         | 
				
			|||
        try { | 
				
			|||
            // Send query to server | 
				
			|||
            connection.sendMessage((byte)0x03, sql.getBytes()); // JDBC_MSG_EXECUTE_QUERY | 
				
			|||
             | 
				
			|||
            // Read response | 
				
			|||
            SeaweedFSConnection.Response response = connection.readResponse(); | 
				
			|||
             | 
				
			|||
            if (response.type == (byte)0x01) { // JDBC_RESP_ERROR | 
				
			|||
                throw new SQLException("Query failed: " + new String(response.data)); | 
				
			|||
            } else if (response.type == (byte)0x02) { // JDBC_RESP_RESULT_SET | 
				
			|||
                // Parse result set data | 
				
			|||
                currentResultSet = new SeaweedFSResultSet(this, response.data); | 
				
			|||
                updateCount = -1; | 
				
			|||
                return currentResultSet; | 
				
			|||
            } else { | 
				
			|||
                throw new SQLException("Unexpected response type: " + response.type); | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
        } catch (Exception e) { | 
				
			|||
            throw new SQLException("Failed to execute query: " + e.getMessage(), e); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int executeUpdate(String sql) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        logger.debug("Executing update: {}", sql); | 
				
			|||
         | 
				
			|||
        try { | 
				
			|||
            // Send update to server | 
				
			|||
            connection.sendMessage((byte)0x04, sql.getBytes()); // JDBC_MSG_EXECUTE_UPDATE | 
				
			|||
             | 
				
			|||
            // Read response | 
				
			|||
            SeaweedFSConnection.Response response = connection.readResponse(); | 
				
			|||
             | 
				
			|||
            if (response.type == (byte)0x01) { // JDBC_RESP_ERROR | 
				
			|||
                throw new SQLException("Update failed: " + new String(response.data)); | 
				
			|||
            } else if (response.type == (byte)0x03) { // JDBC_RESP_UPDATE_COUNT | 
				
			|||
                // Parse update count | 
				
			|||
                updateCount = parseUpdateCount(response.data); | 
				
			|||
                currentResultSet = null; | 
				
			|||
                return updateCount; | 
				
			|||
            } else { | 
				
			|||
                throw new SQLException("Unexpected response type: " + response.type); | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
        } catch (Exception e) { | 
				
			|||
            throw new SQLException("Failed to execute update: " + e.getMessage(), e); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void close() throws SQLException { | 
				
			|||
        if (!closed) { | 
				
			|||
            if (currentResultSet != null) { | 
				
			|||
                currentResultSet.close(); | 
				
			|||
                currentResultSet = null; | 
				
			|||
            } | 
				
			|||
            closed = true; | 
				
			|||
            logger.debug("Statement closed"); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxFieldSize() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return 0; // No limit | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setMaxFieldSize(int max) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        // No-op | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getMaxRows() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return maxRows; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setMaxRows(int max) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        this.maxRows = max; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setEscapeProcessing(boolean enable) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        // No-op | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getQueryTimeout() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return queryTimeout; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setQueryTimeout(int seconds) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        this.queryTimeout = seconds; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void cancel() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        // No-op - cancellation not supported | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public SQLWarning getWarnings() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return null; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void clearWarnings() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        // No-op | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setCursorName(String name) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        // No-op - cursors not supported | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean execute(String sql) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        logger.debug("Executing: {}", sql); | 
				
			|||
         | 
				
			|||
        // Determine if this is likely a query or update | 
				
			|||
        String trimmedSql = sql.trim().toUpperCase(); | 
				
			|||
        if (trimmedSql.startsWith("SELECT") ||  | 
				
			|||
            trimmedSql.startsWith("SHOW") ||  | 
				
			|||
            trimmedSql.startsWith("DESCRIBE") || | 
				
			|||
            trimmedSql.startsWith("DESC") || | 
				
			|||
            trimmedSql.startsWith("EXPLAIN")) { | 
				
			|||
            // It's a query | 
				
			|||
            executeQuery(sql); | 
				
			|||
            return true; | 
				
			|||
        } else { | 
				
			|||
            // It's an update | 
				
			|||
            executeUpdate(sql); | 
				
			|||
            return false; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getResultSet() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return currentResultSet; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getUpdateCount() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return updateCount; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean getMoreResults() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        if (currentResultSet != null) { | 
				
			|||
            currentResultSet.close(); | 
				
			|||
            currentResultSet = null; | 
				
			|||
        } | 
				
			|||
        updateCount = -1; | 
				
			|||
        return false; // No more results | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setFetchDirection(int direction) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        if (direction != ResultSet.FETCH_FORWARD) { | 
				
			|||
            throw new SQLException("Only FETCH_FORWARD is supported"); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getFetchDirection() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return ResultSet.FETCH_FORWARD; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setFetchSize(int rows) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        this.fetchSize = rows; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getFetchSize() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return fetchSize; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getResultSetConcurrency() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return ResultSet.CONCUR_READ_ONLY; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getResultSetType() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return ResultSet.TYPE_FORWARD_ONLY; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void addBatch(String sql) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        batch.add(sql); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void clearBatch() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        batch.clear(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int[] executeBatch() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        int[] results = new int[batch.size()]; | 
				
			|||
         | 
				
			|||
        for (int i = 0; i < batch.size(); i++) { | 
				
			|||
            try { | 
				
			|||
                results[i] = executeUpdate(batch.get(i)); | 
				
			|||
            } catch (SQLException e) { | 
				
			|||
                results[i] = EXECUTE_FAILED; | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
         | 
				
			|||
        batch.clear(); | 
				
			|||
        return results; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public Connection getConnection() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return connection; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean getMoreResults(int current) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return getMoreResults(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public ResultSet getGeneratedKeys() throws SQLException { | 
				
			|||
        throw new SQLFeatureNotSupportedException("Generated keys are not supported"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { | 
				
			|||
        return executeUpdate(sql); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { | 
				
			|||
        return executeUpdate(sql); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int executeUpdate(String sql, String[] columnNames) throws SQLException { | 
				
			|||
        return executeUpdate(sql); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { | 
				
			|||
        return execute(sql); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean execute(String sql, int[] columnIndexes) throws SQLException { | 
				
			|||
        return execute(sql); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean execute(String sql, String[] columnNames) throws SQLException { | 
				
			|||
        return execute(sql); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public int getResultSetHoldability() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return ResultSet.CLOSE_CURSORS_AT_COMMIT; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isClosed() throws SQLException { | 
				
			|||
        return closed; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void setPoolable(boolean poolable) throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        // No-op | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isPoolable() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public void closeOnCompletion() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        // No-op | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isCloseOnCompletion() throws SQLException { | 
				
			|||
        checkClosed(); | 
				
			|||
        return false; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public <T> T unwrap(Class<T> iface) throws SQLException { | 
				
			|||
        if (iface.isAssignableFrom(getClass())) { | 
				
			|||
            return iface.cast(this); | 
				
			|||
        } | 
				
			|||
        throw new SQLException("Cannot unwrap to " + iface.getName()); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Override | 
				
			|||
    public boolean isWrapperFor(Class<?> iface) throws SQLException { | 
				
			|||
        return iface.isAssignableFrom(getClass()); | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    protected void checkClosed() throws SQLException { | 
				
			|||
        if (closed) { | 
				
			|||
            throw new SQLException("Statement is closed"); | 
				
			|||
        } | 
				
			|||
        if (connection.isClosed()) { | 
				
			|||
            throw new SQLException("Connection is closed"); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
     | 
				
			|||
    private int parseUpdateCount(byte[] data) { | 
				
			|||
        if (data.length >= 4) { | 
				
			|||
            return ((data[0] & 0xFF) << 24) | | 
				
			|||
                   ((data[1] & 0xFF) << 16) | | 
				
			|||
                   ((data[2] & 0xFF) << 8) | | 
				
			|||
                   (data[3] & 0xFF); | 
				
			|||
        } | 
				
			|||
        return 0; | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1 @@ | 
				
			|||
com.seaweedfs.jdbc.SeaweedFSDriver | 
				
			|||
@ -0,0 +1,75 @@ | 
				
			|||
package com.seaweedfs.jdbc; | 
				
			|||
 | 
				
			|||
import org.junit.jupiter.api.Test; | 
				
			|||
import static org.junit.jupiter.api.Assertions.*; | 
				
			|||
 | 
				
			|||
import java.sql.DriverManager; | 
				
			|||
import java.sql.SQLException; | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * Basic tests for SeaweedFS JDBC driver | 
				
			|||
 */ | 
				
			|||
public class SeaweedFSDriverTest { | 
				
			|||
 | 
				
			|||
    @Test | 
				
			|||
    public void testDriverRegistration() { | 
				
			|||
        // Driver should be automatically registered via META-INF/services | 
				
			|||
        assertDoesNotThrow(() -> { | 
				
			|||
            Class.forName("com.seaweedfs.jdbc.SeaweedFSDriver"); | 
				
			|||
        }); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Test | 
				
			|||
    public void testURLAcceptance() throws SQLException { | 
				
			|||
        SeaweedFSDriver driver = new SeaweedFSDriver(); | 
				
			|||
         | 
				
			|||
        // Valid URLs | 
				
			|||
        assertTrue(driver.acceptsURL("jdbc:seaweedfs://localhost:8089/default")); | 
				
			|||
        assertTrue(driver.acceptsURL("jdbc:seaweedfs://server:9000/test")); | 
				
			|||
        assertTrue(driver.acceptsURL("jdbc:seaweedfs://192.168.1.100:8089/mydb")); | 
				
			|||
         | 
				
			|||
        // Invalid URLs | 
				
			|||
        assertFalse(driver.acceptsURL("jdbc:mysql://localhost:3306/test")); | 
				
			|||
        assertFalse(driver.acceptsURL("jdbc:postgresql://localhost:5432/test")); | 
				
			|||
        assertFalse(driver.acceptsURL(null)); | 
				
			|||
        assertFalse(driver.acceptsURL("")); | 
				
			|||
        assertFalse(driver.acceptsURL("not-a-url")); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Test | 
				
			|||
    public void testDriverInfo() { | 
				
			|||
        SeaweedFSDriver driver = new SeaweedFSDriver(); | 
				
			|||
         | 
				
			|||
        assertEquals(SeaweedFSDriver.DRIVER_MAJOR_VERSION, driver.getMajorVersion()); | 
				
			|||
        assertEquals(SeaweedFSDriver.DRIVER_MINOR_VERSION, driver.getMinorVersion()); | 
				
			|||
        assertFalse(driver.jdbcCompliant()); // We're not fully JDBC compliant | 
				
			|||
         | 
				
			|||
        assertNotNull(SeaweedFSDriver.getDriverInfo()); | 
				
			|||
        assertTrue(SeaweedFSDriver.getDriverInfo().contains("SeaweedFS")); | 
				
			|||
        assertTrue(SeaweedFSDriver.getDriverInfo().contains("JDBC")); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    @Test | 
				
			|||
    public void testPropertyInfo() throws SQLException { | 
				
			|||
        SeaweedFSDriver driver = new SeaweedFSDriver(); | 
				
			|||
         | 
				
			|||
        var properties = driver.getPropertyInfo("jdbc:seaweedfs://localhost:8089/default", null); | 
				
			|||
        assertNotNull(properties); | 
				
			|||
        assertTrue(properties.length > 0); | 
				
			|||
         | 
				
			|||
        // Check that basic properties are present | 
				
			|||
        boolean foundHost = false, foundPort = false, foundDatabase = false; | 
				
			|||
        for (var prop : properties) { | 
				
			|||
            if ("host".equals(prop.name)) foundHost = true; | 
				
			|||
            if ("port".equals(prop.name)) foundPort = true; | 
				
			|||
            if ("database".equals(prop.name)) foundDatabase = true; | 
				
			|||
        } | 
				
			|||
         | 
				
			|||
        assertTrue(foundHost, "Host property should be present"); | 
				
			|||
        assertTrue(foundPort, "Port property should be present"); | 
				
			|||
        assertTrue(foundDatabase, "Database property should be present"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    // Note: Connection tests would require a running SeaweedFS JDBC server | 
				
			|||
    // These tests would be part of integration tests, not unit tests | 
				
			|||
} | 
				
			|||
@ -0,0 +1,141 @@ | 
				
			|||
package command | 
				
			|||
 | 
				
			|||
import ( | 
				
			|||
	"context" | 
				
			|||
	"fmt" | 
				
			|||
	"os" | 
				
			|||
	"os/signal" | 
				
			|||
	"syscall" | 
				
			|||
	"time" | 
				
			|||
 | 
				
			|||
	weed_server "github.com/seaweedfs/seaweedfs/weed/server" | 
				
			|||
	"github.com/seaweedfs/seaweedfs/weed/util" | 
				
			|||
) | 
				
			|||
 | 
				
			|||
var ( | 
				
			|||
	jdbcOptions JdbcOptions | 
				
			|||
) | 
				
			|||
 | 
				
			|||
type JdbcOptions struct { | 
				
			|||
	host       *string | 
				
			|||
	port       *int | 
				
			|||
	masterAddr *string | 
				
			|||
} | 
				
			|||
 | 
				
			|||
func init() { | 
				
			|||
	cmdJdbc.Run = runJdbc // break init cycle
 | 
				
			|||
	jdbcOptions.host = cmdJdbc.Flag.String("host", "localhost", "JDBC server host") | 
				
			|||
	jdbcOptions.port = cmdJdbc.Flag.Int("port", 8089, "JDBC server port") | 
				
			|||
	jdbcOptions.masterAddr = cmdJdbc.Flag.String("master", "localhost:9333", "SeaweedFS master server address") | 
				
			|||
} | 
				
			|||
 | 
				
			|||
var cmdJdbc = &Command{ | 
				
			|||
	UsageLine: "jdbc -port=8089 -master=<master_server>", | 
				
			|||
	Short:     "start a JDBC server for SQL queries", | 
				
			|||
	Long: `Start a JDBC server that provides SQL query access to SeaweedFS. | 
				
			|||
	 | 
				
			|||
This JDBC server allows standard JDBC clients and tools to connect to SeaweedFS | 
				
			|||
and execute SQL queries against MQ topics. It implements a subset of the JDBC | 
				
			|||
protocol for compatibility with most database tools and applications. | 
				
			|||
 | 
				
			|||
Examples: | 
				
			|||
 | 
				
			|||
	# Start JDBC server on default port 8089 | 
				
			|||
	weed jdbc | 
				
			|||
	 | 
				
			|||
	# Start on custom port with specific master | 
				
			|||
	weed jdbc -port=8090 -master=master1:9333 | 
				
			|||
	 | 
				
			|||
	# Allow connections from any host | 
				
			|||
	weed jdbc -host=0.0.0.0 -port=8089 | 
				
			|||
 | 
				
			|||
Clients can then connect using JDBC URL: | 
				
			|||
	jdbc:seaweedfs://hostname:port/database
 | 
				
			|||
 | 
				
			|||
Supported SQL operations: | 
				
			|||
	- SELECT queries on MQ topics | 
				
			|||
	- DESCRIBE/DESC commands | 
				
			|||
	- SHOW DATABASES/TABLES commands | 
				
			|||
	- Aggregation functions (COUNT, SUM, AVG, MIN, MAX) | 
				
			|||
	- WHERE clauses with filtering | 
				
			|||
	- System columns (_timestamp_ns, _key, _source) | 
				
			|||
 | 
				
			|||
Compatible with: | 
				
			|||
	- Standard JDBC tools (DBeaver, IntelliJ DataGrip, etc.) | 
				
			|||
	- Business Intelligence tools (Tableau, Power BI, etc.) | 
				
			|||
	- Java applications using JDBC drivers | 
				
			|||
	- SQL reporting tools | 
				
			|||
 | 
				
			|||
`, | 
				
			|||
} | 
				
			|||
 | 
				
			|||
func runJdbc(cmd *Command, args []string) bool { | 
				
			|||
 | 
				
			|||
	util.LoadConfiguration("security", false) | 
				
			|||
 | 
				
			|||
	// Validate options
 | 
				
			|||
	if *jdbcOptions.masterAddr == "" { | 
				
			|||
		fmt.Fprintf(os.Stderr, "Error: master address is required\n") | 
				
			|||
		return false | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	// Create JDBC server
 | 
				
			|||
	jdbcServer, err := weed_server.NewJDBCServer(*jdbcOptions.host, *jdbcOptions.port, *jdbcOptions.masterAddr) | 
				
			|||
	if err != nil { | 
				
			|||
		fmt.Fprintf(os.Stderr, "Error creating JDBC server: %v\n", err) | 
				
			|||
		return false | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	// Start the server
 | 
				
			|||
	fmt.Printf("Starting SeaweedFS JDBC Server...\n") | 
				
			|||
	fmt.Printf("Host: %s\n", *jdbcOptions.host) | 
				
			|||
	fmt.Printf("Port: %d\n", *jdbcOptions.port) | 
				
			|||
	fmt.Printf("Master: %s\n", *jdbcOptions.masterAddr) | 
				
			|||
	fmt.Printf("\nJDBC URL: jdbc:seaweedfs://%s:%d/default\n", *jdbcOptions.host, *jdbcOptions.port) | 
				
			|||
	fmt.Printf("\nSupported operations:\n") | 
				
			|||
	fmt.Printf("  - SELECT queries on MQ topics\n") | 
				
			|||
	fmt.Printf("  - DESCRIBE/DESC table_name\n") | 
				
			|||
	fmt.Printf("  - SHOW DATABASES\n") | 
				
			|||
	fmt.Printf("  - SHOW TABLES\n") | 
				
			|||
	fmt.Printf("  - Aggregations: COUNT, SUM, AVG, MIN, MAX\n") | 
				
			|||
	fmt.Printf("  - System columns: _timestamp_ns, _key, _source\n") | 
				
			|||
	fmt.Printf("\nReady for JDBC connections!\n\n") | 
				
			|||
 | 
				
			|||
	err = jdbcServer.Start() | 
				
			|||
	if err != nil { | 
				
			|||
		fmt.Fprintf(os.Stderr, "Error starting JDBC server: %v\n", err) | 
				
			|||
		return false | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	// Set up signal handling for graceful shutdown
 | 
				
			|||
	sigChan := make(chan os.Signal, 1) | 
				
			|||
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) | 
				
			|||
 | 
				
			|||
	// Wait for shutdown signal
 | 
				
			|||
	<-sigChan | 
				
			|||
	fmt.Printf("\nReceived shutdown signal, stopping JDBC server...\n") | 
				
			|||
 | 
				
			|||
	// Create context with timeout for graceful shutdown
 | 
				
			|||
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | 
				
			|||
	defer cancel() | 
				
			|||
 | 
				
			|||
	// Stop the server with timeout
 | 
				
			|||
	done := make(chan error, 1) | 
				
			|||
	go func() { | 
				
			|||
		done <- jdbcServer.Stop() | 
				
			|||
	}() | 
				
			|||
 | 
				
			|||
	select { | 
				
			|||
	case err := <-done: | 
				
			|||
		if err != nil { | 
				
			|||
			fmt.Fprintf(os.Stderr, "Error stopping JDBC server: %v\n", err) | 
				
			|||
			return false | 
				
			|||
		} | 
				
			|||
		fmt.Printf("JDBC server stopped successfully\n") | 
				
			|||
	case <-ctx.Done(): | 
				
			|||
		fmt.Fprintf(os.Stderr, "Timeout waiting for JDBC server to stop\n") | 
				
			|||
		return false | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	return true | 
				
			|||
} | 
				
			|||
@ -0,0 +1,524 @@ | 
				
			|||
package weed_server | 
				
			|||
 | 
				
			|||
import ( | 
				
			|||
	"bufio" | 
				
			|||
	"context" | 
				
			|||
	"encoding/binary" | 
				
			|||
	"fmt" | 
				
			|||
	"io" | 
				
			|||
	"net" | 
				
			|||
	"sync" | 
				
			|||
	"time" | 
				
			|||
 | 
				
			|||
	"github.com/seaweedfs/seaweedfs/weed/glog" | 
				
			|||
	"github.com/seaweedfs/seaweedfs/weed/query/engine" | 
				
			|||
	"github.com/seaweedfs/seaweedfs/weed/query/sqltypes" | 
				
			|||
) | 
				
			|||
 | 
				
			|||
// JDBCServer provides JDBC-compatible access to SeaweedFS SQL engine
 | 
				
			|||
type JDBCServer struct { | 
				
			|||
	host        string | 
				
			|||
	port        int | 
				
			|||
	masterAddr  string | 
				
			|||
	listener    net.Listener | 
				
			|||
	sqlEngine   *engine.SQLEngine | 
				
			|||
	connections map[net.Conn]*JDBCConnection | 
				
			|||
	connMutex   sync.RWMutex | 
				
			|||
	shutdown    chan struct{} | 
				
			|||
	wg          sync.WaitGroup | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// JDBCConnection represents a single JDBC client connection
 | 
				
			|||
type JDBCConnection struct { | 
				
			|||
	conn         net.Conn | 
				
			|||
	reader       *bufio.Reader | 
				
			|||
	writer       *bufio.Writer | 
				
			|||
	database     string | 
				
			|||
	autoCommit   bool | 
				
			|||
	connectionID uint32 | 
				
			|||
	closed       bool | 
				
			|||
	mutex        sync.Mutex | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// JDBC Protocol Constants
 | 
				
			|||
const ( | 
				
			|||
	// Message Types
 | 
				
			|||
	JDBC_MSG_CONNECT        = 0x01 | 
				
			|||
	JDBC_MSG_DISCONNECT     = 0x02 | 
				
			|||
	JDBC_MSG_EXECUTE_QUERY  = 0x03 | 
				
			|||
	JDBC_MSG_EXECUTE_UPDATE = 0x04 | 
				
			|||
	JDBC_MSG_PREPARE        = 0x05 | 
				
			|||
	JDBC_MSG_EXECUTE_PREP   = 0x06 | 
				
			|||
	JDBC_MSG_GET_METADATA   = 0x07 | 
				
			|||
	JDBC_MSG_SET_AUTOCOMMIT = 0x08 | 
				
			|||
	JDBC_MSG_COMMIT         = 0x09 | 
				
			|||
	JDBC_MSG_ROLLBACK       = 0x0A | 
				
			|||
 | 
				
			|||
	// Response Types
 | 
				
			|||
	JDBC_RESP_OK           = 0x00 | 
				
			|||
	JDBC_RESP_ERROR        = 0x01 | 
				
			|||
	JDBC_RESP_RESULT_SET   = 0x02 | 
				
			|||
	JDBC_RESP_UPDATE_COUNT = 0x03 | 
				
			|||
	JDBC_RESP_METADATA     = 0x04 | 
				
			|||
 | 
				
			|||
	// Default values
 | 
				
			|||
	DEFAULT_JDBC_PORT = 8089 | 
				
			|||
) | 
				
			|||
 | 
				
			|||
// NewJDBCServer creates a new JDBC server instance
 | 
				
			|||
func NewJDBCServer(host string, port int, masterAddr string) (*JDBCServer, error) { | 
				
			|||
	if port <= 0 { | 
				
			|||
		port = DEFAULT_JDBC_PORT | 
				
			|||
	} | 
				
			|||
	if host == "" { | 
				
			|||
		host = "localhost" | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	// Create SQL engine
 | 
				
			|||
	sqlEngine := engine.NewSQLEngine(masterAddr) | 
				
			|||
 | 
				
			|||
	server := &JDBCServer{ | 
				
			|||
		host:        host, | 
				
			|||
		port:        port, | 
				
			|||
		masterAddr:  masterAddr, | 
				
			|||
		sqlEngine:   sqlEngine, | 
				
			|||
		connections: make(map[net.Conn]*JDBCConnection), | 
				
			|||
		shutdown:    make(chan struct{}), | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	return server, nil | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// Start begins listening for JDBC connections
 | 
				
			|||
func (s *JDBCServer) Start() error { | 
				
			|||
	addr := fmt.Sprintf("%s:%d", s.host, s.port) | 
				
			|||
	listener, err := net.Listen("tcp", addr) | 
				
			|||
	if err != nil { | 
				
			|||
		return fmt.Errorf("failed to start JDBC server on %s: %v", addr, err) | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	s.listener = listener | 
				
			|||
	glog.Infof("JDBC Server listening on %s", addr) | 
				
			|||
 | 
				
			|||
	s.wg.Add(1) | 
				
			|||
	go s.acceptConnections() | 
				
			|||
 | 
				
			|||
	return nil | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// Stop gracefully shuts down the JDBC server
 | 
				
			|||
func (s *JDBCServer) Stop() error { | 
				
			|||
	close(s.shutdown) | 
				
			|||
 | 
				
			|||
	if s.listener != nil { | 
				
			|||
		s.listener.Close() | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	// Close all connections
 | 
				
			|||
	s.connMutex.Lock() | 
				
			|||
	for conn, jdbcConn := range s.connections { | 
				
			|||
		jdbcConn.close() | 
				
			|||
		conn.Close() | 
				
			|||
	} | 
				
			|||
	s.connections = make(map[net.Conn]*JDBCConnection) | 
				
			|||
	s.connMutex.Unlock() | 
				
			|||
 | 
				
			|||
	s.wg.Wait() | 
				
			|||
	glog.Infof("JDBC Server stopped") | 
				
			|||
	return nil | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// acceptConnections handles incoming JDBC connections
 | 
				
			|||
func (s *JDBCServer) acceptConnections() { | 
				
			|||
	defer s.wg.Done() | 
				
			|||
 | 
				
			|||
	for { | 
				
			|||
		select { | 
				
			|||
		case <-s.shutdown: | 
				
			|||
			return | 
				
			|||
		default: | 
				
			|||
		} | 
				
			|||
 | 
				
			|||
		conn, err := s.listener.Accept() | 
				
			|||
		if err != nil { | 
				
			|||
			select { | 
				
			|||
			case <-s.shutdown: | 
				
			|||
				return | 
				
			|||
			default: | 
				
			|||
				glog.Errorf("Failed to accept JDBC connection: %v", err) | 
				
			|||
				continue | 
				
			|||
			} | 
				
			|||
		} | 
				
			|||
 | 
				
			|||
		s.wg.Add(1) | 
				
			|||
		go s.handleConnection(conn) | 
				
			|||
	} | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// handleConnection processes a single JDBC connection
 | 
				
			|||
func (s *JDBCServer) handleConnection(conn net.Conn) { | 
				
			|||
	defer s.wg.Done() | 
				
			|||
	defer conn.Close() | 
				
			|||
 | 
				
			|||
	// Create JDBC connection wrapper
 | 
				
			|||
	jdbcConn := &JDBCConnection{ | 
				
			|||
		conn:         conn, | 
				
			|||
		reader:       bufio.NewReader(conn), | 
				
			|||
		writer:       bufio.NewWriter(conn), | 
				
			|||
		database:     "default", | 
				
			|||
		autoCommit:   true, | 
				
			|||
		connectionID: s.generateConnectionID(), | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	// Register connection
 | 
				
			|||
	s.connMutex.Lock() | 
				
			|||
	s.connections[conn] = jdbcConn | 
				
			|||
	s.connMutex.Unlock() | 
				
			|||
 | 
				
			|||
	// Clean up on exit
 | 
				
			|||
	defer func() { | 
				
			|||
		s.connMutex.Lock() | 
				
			|||
		delete(s.connections, conn) | 
				
			|||
		s.connMutex.Unlock() | 
				
			|||
	}() | 
				
			|||
 | 
				
			|||
	glog.Infof("New JDBC connection from %s (ID: %d)", conn.RemoteAddr(), jdbcConn.connectionID) | 
				
			|||
 | 
				
			|||
	// Handle connection messages
 | 
				
			|||
	for { | 
				
			|||
		select { | 
				
			|||
		case <-s.shutdown: | 
				
			|||
			return | 
				
			|||
		default: | 
				
			|||
		} | 
				
			|||
 | 
				
			|||
		// Set read timeout
 | 
				
			|||
		conn.SetReadDeadline(time.Now().Add(30 * time.Second)) | 
				
			|||
 | 
				
			|||
		err := s.handleMessage(jdbcConn) | 
				
			|||
		if err != nil { | 
				
			|||
			if err == io.EOF { | 
				
			|||
				glog.Infof("JDBC client disconnected (ID: %d)", jdbcConn.connectionID) | 
				
			|||
			} else { | 
				
			|||
				glog.Errorf("Error handling JDBC message (ID: %d): %v", jdbcConn.connectionID, err) | 
				
			|||
			} | 
				
			|||
			return | 
				
			|||
		} | 
				
			|||
	} | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// handleMessage processes a single JDBC protocol message
 | 
				
			|||
func (s *JDBCServer) handleMessage(conn *JDBCConnection) error { | 
				
			|||
	// Read message header (message type + length)
 | 
				
			|||
	header := make([]byte, 5) | 
				
			|||
	_, err := io.ReadFull(conn.reader, header) | 
				
			|||
	if err != nil { | 
				
			|||
		return err | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	msgType := header[0] | 
				
			|||
	msgLength := binary.BigEndian.Uint32(header[1:5]) | 
				
			|||
 | 
				
			|||
	// Read message body
 | 
				
			|||
	msgBody := make([]byte, msgLength) | 
				
			|||
	if msgLength > 0 { | 
				
			|||
		_, err = io.ReadFull(conn.reader, msgBody) | 
				
			|||
		if err != nil { | 
				
			|||
			return err | 
				
			|||
		} | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	// Process message based on type
 | 
				
			|||
	switch msgType { | 
				
			|||
	case JDBC_MSG_CONNECT: | 
				
			|||
		return s.handleConnect(conn, msgBody) | 
				
			|||
	case JDBC_MSG_DISCONNECT: | 
				
			|||
		return s.handleDisconnect(conn) | 
				
			|||
	case JDBC_MSG_EXECUTE_QUERY: | 
				
			|||
		return s.handleExecuteQuery(conn, msgBody) | 
				
			|||
	case JDBC_MSG_EXECUTE_UPDATE: | 
				
			|||
		return s.handleExecuteUpdate(conn, msgBody) | 
				
			|||
	case JDBC_MSG_GET_METADATA: | 
				
			|||
		return s.handleGetMetadata(conn, msgBody) | 
				
			|||
	case JDBC_MSG_SET_AUTOCOMMIT: | 
				
			|||
		return s.handleSetAutoCommit(conn, msgBody) | 
				
			|||
	case JDBC_MSG_COMMIT: | 
				
			|||
		return s.handleCommit(conn) | 
				
			|||
	case JDBC_MSG_ROLLBACK: | 
				
			|||
		return s.handleRollback(conn) | 
				
			|||
	default: | 
				
			|||
		return s.sendError(conn, fmt.Errorf("unknown message type: %d", msgType)) | 
				
			|||
	} | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// handleConnect processes JDBC connection request
 | 
				
			|||
func (s *JDBCServer) handleConnect(conn *JDBCConnection, msgBody []byte) error { | 
				
			|||
	// Parse connection string (database name)
 | 
				
			|||
	if len(msgBody) > 0 { | 
				
			|||
		conn.database = string(msgBody) | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	glog.Infof("JDBC client connected to database: %s (ID: %d)", conn.database, conn.connectionID) | 
				
			|||
 | 
				
			|||
	// Send OK response
 | 
				
			|||
	return s.sendOK(conn, "Connected successfully") | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// handleDisconnect processes JDBC disconnect request
 | 
				
			|||
func (s *JDBCServer) handleDisconnect(conn *JDBCConnection) error { | 
				
			|||
	glog.Infof("JDBC client disconnecting (ID: %d)", conn.connectionID) | 
				
			|||
	conn.close() | 
				
			|||
	return io.EOF // This will cause the connection handler to exit
 | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// handleExecuteQuery processes SQL SELECT queries
 | 
				
			|||
func (s *JDBCServer) handleExecuteQuery(conn *JDBCConnection, msgBody []byte) error { | 
				
			|||
	sql := string(msgBody) | 
				
			|||
 | 
				
			|||
	glog.V(2).Infof("Executing query (ID: %d): %s", conn.connectionID, sql) | 
				
			|||
 | 
				
			|||
	// Execute SQL using the query engine
 | 
				
			|||
	ctx := context.Background() | 
				
			|||
	result, err := s.sqlEngine.ExecuteSQL(ctx, sql) | 
				
			|||
	if err != nil { | 
				
			|||
		return s.sendError(conn, err) | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	if result.Error != nil { | 
				
			|||
		return s.sendError(conn, result.Error) | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	// Send result set
 | 
				
			|||
	return s.sendResultSet(conn, result) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// handleExecuteUpdate processes SQL UPDATE/INSERT/DELETE queries
 | 
				
			|||
func (s *JDBCServer) handleExecuteUpdate(conn *JDBCConnection, msgBody []byte) error { | 
				
			|||
	sql := string(msgBody) | 
				
			|||
 | 
				
			|||
	glog.V(2).Infof("Executing update (ID: %d): %s", conn.connectionID, sql) | 
				
			|||
 | 
				
			|||
	// For now, treat updates same as queries since SeaweedFS SQL is read-only
 | 
				
			|||
	ctx := context.Background() | 
				
			|||
	result, err := s.sqlEngine.ExecuteSQL(ctx, sql) | 
				
			|||
	if err != nil { | 
				
			|||
		return s.sendError(conn, err) | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	if result.Error != nil { | 
				
			|||
		return s.sendError(conn, result.Error) | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	// Send update count (0 for read-only operations)
 | 
				
			|||
	return s.sendUpdateCount(conn, 0) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// handleGetMetadata processes JDBC metadata requests
 | 
				
			|||
func (s *JDBCServer) handleGetMetadata(conn *JDBCConnection, msgBody []byte) error { | 
				
			|||
	metadataType := string(msgBody) | 
				
			|||
 | 
				
			|||
	glog.V(2).Infof("Getting metadata (ID: %d): %s", conn.connectionID, metadataType) | 
				
			|||
 | 
				
			|||
	switch metadataType { | 
				
			|||
	case "tables": | 
				
			|||
		return s.sendTablesMetadata(conn) | 
				
			|||
	case "databases", "schemas": | 
				
			|||
		return s.sendDatabasesMetadata(conn) | 
				
			|||
	default: | 
				
			|||
		return s.sendError(conn, fmt.Errorf("unsupported metadata type: %s", metadataType)) | 
				
			|||
	} | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// handleSetAutoCommit processes autocommit setting
 | 
				
			|||
func (s *JDBCServer) handleSetAutoCommit(conn *JDBCConnection, msgBody []byte) error { | 
				
			|||
	autoCommit := len(msgBody) > 0 && msgBody[0] == 1 | 
				
			|||
	conn.autoCommit = autoCommit | 
				
			|||
 | 
				
			|||
	glog.V(2).Infof("Setting autocommit (ID: %d): %v", conn.connectionID, autoCommit) | 
				
			|||
 | 
				
			|||
	return s.sendOK(conn, fmt.Sprintf("AutoCommit set to %v", autoCommit)) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// handleCommit processes transaction commit (no-op for read-only)
 | 
				
			|||
func (s *JDBCServer) handleCommit(conn *JDBCConnection) error { | 
				
			|||
	glog.V(2).Infof("Commit (ID: %d): no-op for read-only", conn.connectionID) | 
				
			|||
	return s.sendOK(conn, "Commit successful") | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// handleRollback processes transaction rollback (no-op for read-only)
 | 
				
			|||
func (s *JDBCServer) handleRollback(conn *JDBCConnection) error { | 
				
			|||
	glog.V(2).Infof("Rollback (ID: %d): no-op for read-only", conn.connectionID) | 
				
			|||
	return s.sendOK(conn, "Rollback successful") | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// sendOK sends a success response
 | 
				
			|||
func (s *JDBCServer) sendOK(conn *JDBCConnection, message string) error { | 
				
			|||
	return s.sendResponse(conn, JDBC_RESP_OK, []byte(message)) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// sendError sends an error response
 | 
				
			|||
func (s *JDBCServer) sendError(conn *JDBCConnection, err error) error { | 
				
			|||
	return s.sendResponse(conn, JDBC_RESP_ERROR, []byte(err.Error())) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// sendResultSet sends query results
 | 
				
			|||
func (s *JDBCServer) sendResultSet(conn *JDBCConnection, result *engine.QueryResult) error { | 
				
			|||
	// Serialize result set
 | 
				
			|||
	data := s.serializeResultSet(result) | 
				
			|||
	return s.sendResponse(conn, JDBC_RESP_RESULT_SET, data) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// sendUpdateCount sends update operation result
 | 
				
			|||
func (s *JDBCServer) sendUpdateCount(conn *JDBCConnection, count int) error { | 
				
			|||
	data := make([]byte, 4) | 
				
			|||
	binary.BigEndian.PutUint32(data, uint32(count)) | 
				
			|||
	return s.sendResponse(conn, JDBC_RESP_UPDATE_COUNT, data) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// sendTablesMetadata sends table metadata
 | 
				
			|||
func (s *JDBCServer) sendTablesMetadata(conn *JDBCConnection) error { | 
				
			|||
	// For now, return empty metadata - this would need to query the schema catalog
 | 
				
			|||
	data := s.serializeTablesMetadata([]string{}) | 
				
			|||
	return s.sendResponse(conn, JDBC_RESP_METADATA, data) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// sendDatabasesMetadata sends database/schema metadata
 | 
				
			|||
func (s *JDBCServer) sendDatabasesMetadata(conn *JDBCConnection) error { | 
				
			|||
	// Return default databases
 | 
				
			|||
	databases := []string{"default", "test"} | 
				
			|||
	data := s.serializeDatabasesMetadata(databases) | 
				
			|||
	return s.sendResponse(conn, JDBC_RESP_METADATA, data) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// sendResponse sends a response with the given type and data
 | 
				
			|||
func (s *JDBCServer) sendResponse(conn *JDBCConnection, responseType byte, data []byte) error { | 
				
			|||
	conn.mutex.Lock() | 
				
			|||
	defer conn.mutex.Unlock() | 
				
			|||
 | 
				
			|||
	// Write response header
 | 
				
			|||
	header := make([]byte, 5) | 
				
			|||
	header[0] = responseType | 
				
			|||
	binary.BigEndian.PutUint32(header[1:5], uint32(len(data))) | 
				
			|||
 | 
				
			|||
	_, err := conn.writer.Write(header) | 
				
			|||
	if err != nil { | 
				
			|||
		return err | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	// Write response data
 | 
				
			|||
	if len(data) > 0 { | 
				
			|||
		_, err = conn.writer.Write(data) | 
				
			|||
		if err != nil { | 
				
			|||
			return err | 
				
			|||
		} | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	return conn.writer.Flush() | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// serializeResultSet converts QueryResult to JDBC wire format
 | 
				
			|||
func (s *JDBCServer) serializeResultSet(result *engine.QueryResult) []byte { | 
				
			|||
	var data []byte | 
				
			|||
 | 
				
			|||
	// Column count
 | 
				
			|||
	colCount := make([]byte, 4) | 
				
			|||
	binary.BigEndian.PutUint32(colCount, uint32(len(result.Columns))) | 
				
			|||
	data = append(data, colCount...) | 
				
			|||
 | 
				
			|||
	// Column names
 | 
				
			|||
	for _, col := range result.Columns { | 
				
			|||
		colName := []byte(col) | 
				
			|||
		colLen := make([]byte, 4) | 
				
			|||
		binary.BigEndian.PutUint32(colLen, uint32(len(colName))) | 
				
			|||
		data = append(data, colLen...) | 
				
			|||
		data = append(data, colName...) | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	// Row count
 | 
				
			|||
	rowCount := make([]byte, 4) | 
				
			|||
	binary.BigEndian.PutUint32(rowCount, uint32(len(result.Rows))) | 
				
			|||
	data = append(data, rowCount...) | 
				
			|||
 | 
				
			|||
	// Rows
 | 
				
			|||
	for _, row := range result.Rows { | 
				
			|||
		for _, value := range row { | 
				
			|||
			// Convert value to string and serialize
 | 
				
			|||
			valueStr := s.valueToString(value) | 
				
			|||
			valueBytes := []byte(valueStr) | 
				
			|||
			valueLen := make([]byte, 4) | 
				
			|||
			binary.BigEndian.PutUint32(valueLen, uint32(len(valueBytes))) | 
				
			|||
			data = append(data, valueLen...) | 
				
			|||
			data = append(data, valueBytes...) | 
				
			|||
		} | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	return data | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// serializeTablesMetadata converts table list to wire format
 | 
				
			|||
func (s *JDBCServer) serializeTablesMetadata(tables []string) []byte { | 
				
			|||
	var data []byte | 
				
			|||
 | 
				
			|||
	// Table count
 | 
				
			|||
	tableCount := make([]byte, 4) | 
				
			|||
	binary.BigEndian.PutUint32(tableCount, uint32(len(tables))) | 
				
			|||
	data = append(data, tableCount...) | 
				
			|||
 | 
				
			|||
	// Table names
 | 
				
			|||
	for _, table := range tables { | 
				
			|||
		tableBytes := []byte(table) | 
				
			|||
		tableLen := make([]byte, 4) | 
				
			|||
		binary.BigEndian.PutUint32(tableLen, uint32(len(tableBytes))) | 
				
			|||
		data = append(data, tableLen...) | 
				
			|||
		data = append(data, tableBytes...) | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	return data | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// serializeDatabasesMetadata converts database list to wire format
 | 
				
			|||
func (s *JDBCServer) serializeDatabasesMetadata(databases []string) []byte { | 
				
			|||
	var data []byte | 
				
			|||
 | 
				
			|||
	// Database count
 | 
				
			|||
	dbCount := make([]byte, 4) | 
				
			|||
	binary.BigEndian.PutUint32(dbCount, uint32(len(databases))) | 
				
			|||
	data = append(data, dbCount...) | 
				
			|||
 | 
				
			|||
	// Database names
 | 
				
			|||
	for _, db := range databases { | 
				
			|||
		dbBytes := []byte(db) | 
				
			|||
		dbLen := make([]byte, 4) | 
				
			|||
		binary.BigEndian.PutUint32(dbLen, uint32(len(dbBytes))) | 
				
			|||
		data = append(data, dbLen...) | 
				
			|||
		data = append(data, dbBytes...) | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	return data | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// valueToString converts a sqltypes.Value to string representation
 | 
				
			|||
func (s *JDBCServer) valueToString(value sqltypes.Value) string { | 
				
			|||
	if value.IsNull() { | 
				
			|||
		return "" | 
				
			|||
	} | 
				
			|||
 | 
				
			|||
	return value.ToString() | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// generateConnectionID generates a unique connection ID
 | 
				
			|||
func (s *JDBCServer) generateConnectionID() uint32 { | 
				
			|||
	return uint32(time.Now().UnixNano() % 1000000) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// close marks the connection as closed
 | 
				
			|||
func (c *JDBCConnection) close() { | 
				
			|||
	c.mutex.Lock() | 
				
			|||
	defer c.mutex.Unlock() | 
				
			|||
	c.closed = true | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// GetAddress returns the server address
 | 
				
			|||
func (s *JDBCServer) GetAddress() string { | 
				
			|||
	return fmt.Sprintf("%s:%d", s.host, s.port) | 
				
			|||
} | 
				
			|||
						Write
						Preview
					
					
					Loading…
					
					Cancel
						Save
					
		Reference in new issue