diff --git a/.gitignore b/.gitignore index 32849a44ac..c4b3f618af 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ hs_err_pid* # mac os # .DS_Store +Library # idea # .idea diff --git a/configure/clean/clean-dependencie.sh b/configure/clean/clean-dependencie.sh index 444af636f8..58e7197346 100644 --- a/configure/clean/clean-dependencie.sh +++ b/configure/clean/clean-dependencie.sh @@ -1,6 +1,8 @@ #!/bin/bash +HOME=$(pwd) echo "Clean dependencies" +cd "$HOME/core/datacap-ui" echo "Task: check dependencies" depcheck --json > depcheck-output.json @@ -20,4 +22,6 @@ done echo "Task: install dependencies again" pnpm install --fix + echo "Clean dependencies done" +cd "$HOME" \ No newline at end of file diff --git a/core/datacap-condor/pom.xml b/core/datacap-condor/pom.xml new file mode 100644 index 0000000000..ed0e4fc2a5 --- /dev/null +++ b/core/datacap-condor/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + io.edurt.datacap + datacap + 2024.4.1-SNAPSHOT + ../../pom.xml + + + datacap-condor + DataCap - Database + + + + io.edurt.datacap + datacap-parser + ${project.version} + + + ch.qos.logback + logback-classic + + + diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/ComparisonOperator.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/ComparisonOperator.java new file mode 100644 index 0000000000..93c5e1d5cb --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/ComparisonOperator.java @@ -0,0 +1,8 @@ +package io.edurt.datacap.condor; + +public enum ComparisonOperator +{ + EQUALS, + GREATER_THAN, + LESS_THAN +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/DataType.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/DataType.java new file mode 100644 index 0000000000..6d2d870960 --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/DataType.java @@ -0,0 +1,9 @@ +package io.edurt.datacap.condor; + +public enum DataType +{ + INTEGER, + VARCHAR, + BOOLEAN, + DOUBLE +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/DatabaseException.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/DatabaseException.java new file mode 100644 index 0000000000..60bbd06f72 --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/DatabaseException.java @@ -0,0 +1,10 @@ +package io.edurt.datacap.condor; + +public class DatabaseException + extends Exception +{ + public DatabaseException(String message) + { + super(message); + } +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/SQLExecutor.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/SQLExecutor.java new file mode 100644 index 0000000000..7a273701d6 --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/SQLExecutor.java @@ -0,0 +1,272 @@ +package io.edurt.datacap.condor; + +import io.edurt.datacap.condor.manager.DatabaseManager; +import io.edurt.datacap.condor.manager.TableManager; +import io.edurt.datacap.condor.metadata.ColumnDefinition; +import io.edurt.datacap.condor.metadata.DatabaseDefinition; +import io.edurt.datacap.condor.metadata.RowDefinition; +import io.edurt.datacap.condor.metadata.TableDefinition; +import io.edurt.datacap.sql.SQLParser; +import io.edurt.datacap.sql.node.ColumnConstraint; +import io.edurt.datacap.sql.node.ConstraintType; +import io.edurt.datacap.sql.node.TableConstraint; +import io.edurt.datacap.sql.node.element.ColumnElement; +import io.edurt.datacap.sql.node.element.SelectElement; +import io.edurt.datacap.sql.node.element.TableElement; +import io.edurt.datacap.sql.statement.CreateDatabaseStatement; +import io.edurt.datacap.sql.statement.CreateTableStatement; +import io.edurt.datacap.sql.statement.DropDatabaseStatement; +import io.edurt.datacap.sql.statement.DropTableStatement; +import io.edurt.datacap.sql.statement.InsertStatement; +import io.edurt.datacap.sql.statement.SQLStatement; +import io.edurt.datacap.sql.statement.SelectStatement; +import io.edurt.datacap.sql.statement.UseDatabaseStatement; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class SQLExecutor +{ + private final DatabaseManager databaseManager; + private TableManager tableManager; + + public SQLExecutor(DatabaseManager databaseManager) + { + this.databaseManager = databaseManager; + } + + public SQLResult execute(String sql) + { + try { + SQLStatement statement = SQLParser.parse(sql); + + if (statement instanceof CreateDatabaseStatement) { + CreateDatabaseStatement createDatabaseStatement = (CreateDatabaseStatement) statement; + return (SQLResult) executeCreateDatabase(createDatabaseStatement); + } + + if (statement instanceof DropDatabaseStatement) { + DropDatabaseStatement dropDatabaseStatement = (DropDatabaseStatement) statement; + return (SQLResult) executeDropDatabase(dropDatabaseStatement); + } + + if (statement instanceof UseDatabaseStatement) { + UseDatabaseStatement useDatabaseStatement = (UseDatabaseStatement) statement; + return (SQLResult) executeUseDatabase(useDatabaseStatement); + } + + if (statement instanceof CreateTableStatement) { + ensureCurrentTableManager(); + CreateTableStatement createTableStatement = (CreateTableStatement) statement; + return (SQLResult) executeCreateTable(createTableStatement); + } + + if (statement instanceof DropTableStatement) { + ensureCurrentTableManager(); + DropTableStatement dropTableStatement = (DropTableStatement) statement; + return (SQLResult) executeDropTable(dropTableStatement); + } + + if (statement instanceof InsertStatement) { + ensureCurrentTableManager(); + InsertStatement insertStatement = (InsertStatement) statement; + return (SQLResult) executeInsert(insertStatement); + } + + if (statement instanceof SelectStatement) { + ensureCurrentTableManager(); + SelectStatement selectStatement = (SelectStatement) statement; + return (SQLResult) executeSelect(selectStatement); + } + + return new SQLResult<>(false, String.format("Unsupported SQL statement: %s", statement)); + } + catch (Exception e) { + return new SQLResult<>(false, e.getMessage()); + } + } + + private void ensureCurrentTableManager() + throws DatabaseException + { + DatabaseDefinition currentDatabase = databaseManager.getCurrentDatabase(); + if (tableManager == null && currentDatabase != null) { + tableManager = currentDatabase.getTableManager(); + } + } + + private SQLResult executeCreateDatabase(CreateDatabaseStatement statement) + { + try { + String databaseName = statement.getDatabaseName(); + + // 检查是否带有 IF NOT EXISTS + // Check if IF NOT EXISTS is present + if (statement.isIfNotExists() && databaseManager.databaseExists(databaseName)) { + return new SQLResult<>(true, "Database already exists"); + } + + // 执行创建数据库 + // Execute database creation + databaseManager.createDatabase(databaseName); + return new SQLResult<>(true, "Database created successfully"); + } + catch (DatabaseException e) { + return new SQLResult<>(false, "Failed to create database: " + e.getMessage()); + } + } + + private SQLResult executeDropDatabase(DropDatabaseStatement statement) + { + try { + if (statement.isIfNotExists() && !databaseManager.databaseExists(statement.getDatabaseName())) { + return new SQLResult<>(true, "Database does not exist"); + } + + databaseManager.dropDatabase(statement.getDatabaseName()); + return new SQLResult<>(true, "Database dropped successfully"); + } + catch (DatabaseException e) { + return new SQLResult<>(false, "Failed to drop database: " + e.getMessage()); + } + } + + private SQLResult executeUseDatabase(UseDatabaseStatement statement) + { + try { + String databaseName = statement.getDatabaseName(); + databaseManager.useDatabase(databaseName); + return new SQLResult<>(true, "Database changed"); + } + catch (DatabaseException e) { + return new SQLResult<>(false, "Failed to use database: " + e.getMessage()); + } + } + + private SQLResult executeCreateTable(CreateTableStatement statement) + { + try { + List columns = convertToColumns(statement.getColumns()); + + TableDefinition metadata = new TableDefinition(statement.getTableName(), columns); + tableManager.createTable(metadata); + return new SQLResult<>(true, "Table created successfully"); + } + catch (Exception e) { + return new SQLResult<>(false, "Failed to create table: " + e.getMessage()); + } + } + + private SQLResult executeDropTable(DropTableStatement statement) + { + try { + tableManager.dropTable(statement.getTableNames().get(0)); + return new SQLResult<>(true, "Table dropped successfully"); + } + catch (Exception e) { + return new SQLResult<>(false, "Failed to drop table: " + e.getMessage()); + } + } + + private SQLResult executeInsert(InsertStatement statement) + { + try { + // TODO: Support check is multiple insert for InsertStatement + if (statement.getSimpleValues().size() == 1) { + tableManager.insert( + statement.getTableName(), + statement.getColumns(), + statement.getSimpleValues().get(0) + ); + } + else { + tableManager.batchInsert( + statement.getTableName(), + statement.getColumns(), + statement.getSimpleValues() + ); + } + + return new SQLResult<>(true, String.format("Inserted %d rows", statement.getSimpleValues().size())); + } + catch (Exception e) { + return new SQLResult<>(false, "Failed to insert rows: " + e.getMessage()); + } + } + + private SQLResult> executeSelect(SelectStatement statement) + { + try { + List rows = tableManager.select( + statement.getFromSources().get(0).getTableName(), + statement.getSelectElements().stream() + .map(SelectElement::getColumn) + .collect(Collectors.toList()), + null + ); + return new SQLResult<>( + true, + String.format("Selected %d rows", rows.size()), + rows + ); + } + catch (Exception e) { + return new SQLResult<>(false, "Failed to select rows: " + e.getMessage()); + } + } + + private List convertToColumns(List elements) + { + List columns = new ArrayList<>(); + + // First pass: collect all columns and primary key constraints + Set primaryKeyColumns = new HashSet<>(); + for (TableElement element : elements) { + if (element instanceof TableConstraint) { + TableConstraint constraint = (TableConstraint) element; + if (constraint.getType() == ConstraintType.PRIMARY_KEY && constraint.getColumns() != null) { + primaryKeyColumns.addAll(Arrays.asList(constraint.getColumns())); + } + } + } + + // Second pass: create column definitions + for (TableElement element : elements) { + if (element instanceof ColumnElement) { + ColumnElement col = (ColumnElement) element; + boolean isPrimaryKey = primaryKeyColumns.contains(col.getColumnName()); + boolean isNullable = true; + + // Check column constraints + for (ColumnConstraint constraint : col.getConstraints()) { + if (constraint.getType() == ConstraintType.PRIMARY_KEY) { + isPrimaryKey = true; + } + else if (constraint.getType() == ConstraintType.NOT_NULL) { + isNullable = false; + } + } + + DataType type = convertDataType(col.getDataType()); + columns.add(new ColumnDefinition( + col.getColumnName(), + type, + isPrimaryKey, + isNullable)); + } + } + + return columns; + } + + private DataType convertDataType(io.edurt.datacap.sql.node.DataType sourceType) + { + // Implement conversion logic from source DataType to target DataType + // This depends on your DataType enum definition + return DataType.valueOf(sourceType.getBaseType()); + } +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/SQLResult.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/SQLResult.java new file mode 100644 index 0000000000..dc7243b6b0 --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/SQLResult.java @@ -0,0 +1,26 @@ +package io.edurt.datacap.condor; + +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class SQLResult +{ + private final boolean success; + private final String message; + private T data; + + public SQLResult(boolean success, String message) + { + this.success = success; + this.message = message; + } + + public SQLResult(boolean success, String message, T data) + { + this.success = success; + this.message = message; + this.data = data; + } +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/TableException.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/TableException.java new file mode 100644 index 0000000000..ed96a7318f --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/TableException.java @@ -0,0 +1,10 @@ +package io.edurt.datacap.condor; + +public class TableException + extends Exception +{ + public TableException(String message) + { + super(message); + } +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/condition/Condition.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/condition/Condition.java new file mode 100644 index 0000000000..a3831fbf3d --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/condition/Condition.java @@ -0,0 +1,8 @@ +package io.edurt.datacap.condor.condition; + +import io.edurt.datacap.condor.metadata.RowDefinition; + +public interface Condition +{ + boolean evaluate(RowDefinition row); +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/condition/SimpleCondition.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/condition/SimpleCondition.java new file mode 100644 index 0000000000..7d7098833b --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/condition/SimpleCondition.java @@ -0,0 +1,42 @@ +package io.edurt.datacap.condor.condition; + +import io.edurt.datacap.condor.ComparisonOperator; +import io.edurt.datacap.condor.metadata.RowDefinition; + +import java.util.Comparator; + +public class SimpleCondition + implements Condition +{ + private final String columnName; + private final Object value; + private final ComparisonOperator operator; + + public SimpleCondition(String columnName, Object value, ComparisonOperator operator) + { + this.columnName = columnName; + this.value = value; + this.operator = operator; + } + + @Override + public boolean evaluate(RowDefinition row) + { + Object rowValue = row.getValue(columnName); + if (rowValue == null || value == null) { + return false; + } + + switch (operator) { + case EQUALS: + return value.equals(rowValue); + case GREATER_THAN: + return Comparator.comparing(Object::toString).compare(rowValue, value) > 0; + case LESS_THAN: + return Comparator.comparing(Object::toString).compare(rowValue, value) < 0; +// return ((Comparable) rowValue).compareTo(value) < 0; + default: + return false; + } + } +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/io/AppendableObjectInputStream.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/io/AppendableObjectInputStream.java new file mode 100644 index 0000000000..ca8c508466 --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/io/AppendableObjectInputStream.java @@ -0,0 +1,30 @@ +package io.edurt.datacap.condor.io; + +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; + +@Slf4j +public class AppendableObjectInputStream + extends ObjectInputStream +{ + private boolean firstObject = true; + + public AppendableObjectInputStream(InputStream in) + throws IOException + { + super(in); + } + + @Override + protected void readStreamHeader() + throws IOException + { + if (firstObject) { + super.readStreamHeader(); + firstObject = false; + } + } +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/io/AppendableObjectOutputStream.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/io/AppendableObjectOutputStream.java new file mode 100644 index 0000000000..d2983ad6d7 --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/io/AppendableObjectOutputStream.java @@ -0,0 +1,27 @@ +package io.edurt.datacap.condor.io; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; + +public class AppendableObjectOutputStream + extends ObjectOutputStream +{ + private boolean firstObject = true; + + public AppendableObjectOutputStream(OutputStream out) + throws IOException + { + super(out); + } + + @Override + protected void writeStreamHeader() + throws IOException + { + if (firstObject) { + super.writeStreamHeader(); + firstObject = false; + } + } +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/manager/DatabaseManager.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/manager/DatabaseManager.java new file mode 100644 index 0000000000..ac7b8417c7 --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/manager/DatabaseManager.java @@ -0,0 +1,206 @@ +package io.edurt.datacap.condor.manager; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.edurt.datacap.condor.DatabaseException; +import io.edurt.datacap.condor.metadata.DatabaseDefinition; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Stream; + +@Slf4j +@SuppressFBWarnings(value = {"RV_RETURN_VALUE_IGNORED_BAD_PRACTICE", "RV_NEGATING_RESULT_OF_COMPARETO"}) +public class DatabaseManager +{ + private static final String ROOT_DIR = "data"; + private Map databases; + private String currentDatabase; + + public static DatabaseManager createManager() + { + return new DatabaseManager(); + } + + private DatabaseManager() + { + this.databases = new HashMap<>(); + initializeRootDirectory(); + loadExistingDatabases(); + } + + private void initializeRootDirectory() + { + File directory = new File(ROOT_DIR); + if (!directory.exists()) { + directory.mkdirs(); + } + } + + private void loadExistingDatabases() + { + log.info("Loading existing databases from {}", ROOT_DIR); + Path rootDir = Path.of(ROOT_DIR); + try (Stream stream = Files.walk(rootDir)) { + stream.filter(path -> Files.isDirectory(path) + && Files.exists(path.resolve("metadata/db.properties"))) + .forEach(path -> { + String dbName = path.getFileName().toString(); + log.debug("Found database: {}", dbName); + databases.put(dbName, new DatabaseDefinition(dbName, path)); + }); + } + catch (IOException e) { + log.error("Failed to load existing databases", e); + } + } + + public void createDatabase(String databaseName) + throws DatabaseException + { + // 验证数据库名称 + // Validate database name + validateDatabaseName(databaseName); + + // 检查数据库是否已存在 + // Check if database already exists + if (databases.containsKey(databaseName)) { + log.debug("Database '{}' already exists", databaseName); + throw new DatabaseException("Database '" + databaseName + "' already exists"); + } + + // 创建数据库目录 + // Create database directory + try { + log.info("Creating database directory: {}", databaseName); + Path dbPath = Paths.get(ROOT_DIR, databaseName); + Files.createDirectory(dbPath); + + // 创建必要的子目录 + // Create necessary subdirectories + log.info("Creating database metadata: {}", databaseName); + Files.createDirectory(dbPath.resolve("tables")); + Files.createDirectory(dbPath.resolve("metadata")); + + // 创建并保存数据库配置 + // Create and save database configuration + log.info("Creating database configuration for database: {}", databaseName); + Properties dbConfig = new Properties(); + dbConfig.setProperty("created_time", String.valueOf(System.currentTimeMillis())); + dbConfig.setProperty("version", "1.0"); + Path configPath = dbPath.resolve("metadata/db.properties"); + try (OutputStream os = Files.newOutputStream(configPath)) { + dbConfig.store(os, "Database Configuration"); + } + + // 创建数据库对象并添加到管理器 + // Create database object and add to manager + DatabaseDefinition database = new DatabaseDefinition(databaseName, dbPath); + databases.put(databaseName, database); + log.info("Database '{}' created successfully", databaseName); + + // 设置为当前数据库 + // Set as current database + currentDatabase = databaseName; + } + catch (IOException e) { + throw new DatabaseException("Failed to create database: " + e.getMessage()); + } + } + + private void validateDatabaseName(String name) + throws DatabaseException + { + log.info("Validating database name: {}", name); + if (name == null || name.trim().isEmpty()) { + throw new DatabaseException("Database name cannot be empty"); + } + + // 检查数据库名称的合法性 + // Check database name validity + if (!name.matches("^[a-zA-Z][a-zA-Z0-9_]*$")) { + throw new DatabaseException("Invalid database name. Database name must start with a letter and can only contain letters, numbers, and underscores"); + } + + // 检查长度限制 + // Check length limit + if (name.length() > 64) { + throw new DatabaseException("Database name is too long (maximum 64 characters)"); + } + } + + public void dropDatabase(String databaseName) + throws DatabaseException + { + if (!databases.containsKey(databaseName)) { + log.info("Database '{}' does not exist", databaseName); + throw new DatabaseException("Database '" + databaseName + "' does not exist"); + } + + try { + // 删除数据库目录及其所有内容 + // Delete database directory and its contents + Path dbPath = Paths.get(ROOT_DIR, databaseName); + try (Stream stream = Files.walk(dbPath)) { + stream.sorted((p1, p2) -> -p1.compareTo(p2)) + .forEach(path -> { + try { + log.debug("Deleting file: {} on database: {}", path, databaseName); + Files.delete(path); + } + catch (IOException e) { + log.error("Failed to delete file: {} on database: {}", path, databaseName, e); + } + }); + } + + // 从管理器中移除数据库 + // Remove database from manager + databases.remove(databaseName); + + // 如果删除的是当前数据库,重置当前数据库 + // Reset current database if deleted database is the current database + if (databaseName.equals(currentDatabase)) { + currentDatabase = null; + } + } + catch (IOException e) { + throw new DatabaseException("Failed to drop database: " + e.getMessage()); + } + } + + public void useDatabase(String databaseName) + throws DatabaseException + { + if (!databases.containsKey(databaseName)) { + throw new DatabaseException("Database '" + databaseName + "' does not exist"); + } + currentDatabase = databaseName; + } + + public DatabaseDefinition getCurrentDatabase() + throws DatabaseException + { + if (currentDatabase == null) { + throw new DatabaseException("No database selected"); + } + return databases.get(currentDatabase); + } + + public boolean databaseExists(String databaseName) + { + return databases.containsKey(databaseName); + } + + public String[] listDatabases() + { + return databases.keySet().toArray(new String[0]); + } +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/manager/TableManager.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/manager/TableManager.java new file mode 100644 index 0000000000..0d0384a098 --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/manager/TableManager.java @@ -0,0 +1,486 @@ +package io.edurt.datacap.condor.manager; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.edurt.datacap.condor.DataType; +import io.edurt.datacap.condor.TableException; +import io.edurt.datacap.condor.condition.Condition; +import io.edurt.datacap.condor.io.AppendableObjectInputStream; +import io.edurt.datacap.condor.io.AppendableObjectOutputStream; +import io.edurt.datacap.condor.metadata.ColumnDefinition; +import io.edurt.datacap.condor.metadata.RowDefinition; +import io.edurt.datacap.condor.metadata.TableDefinition; +import lombok.extern.slf4j.Slf4j; + +import java.io.EOFException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Stream; + +@Slf4j +@SuppressFBWarnings(value = {"DLS_DEAD_LOCAL_STORE", "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"}) +public class TableManager +{ + private final Path dataDir; + private final Map tableMetadataCache; + private final Map tableLocks; + + public TableManager(Path databasePath) + { + this.dataDir = databasePath.resolve("tables"); + this.tableMetadataCache = new HashMap<>(); + this.tableLocks = new HashMap<>(); + initializeDirectory(); + } + + private void initializeDirectory() + { + loadExistingTables(); + } + + private void loadExistingTables() + { + if (!Files.exists(dataDir)) { + return; + } + + log.info("Loading existing tables from {}", dataDir); + try (Stream stream = Files.walk(dataDir, 1)) { + stream.filter(Files::isDirectory) + .filter(path -> !path.equals(dataDir)) + .filter(path -> Files.exists(path.resolve("metadata/table.meta"))) + .forEach(tableDir -> { + String tableName = tableDir.getFileName().toString(); + try { + TableDefinition metadata = loadTableMetadata(tableName); + tableMetadataCache.put(tableName, metadata); + tableLocks.put(tableName, new ReentrantReadWriteLock()); + } + catch (IOException e) { + log.error("Failed to load table metadata: {}", tableName); + } + }); + } + catch (IOException e) { + log.error("Failed to load existing tables", e); + } + } + + public void createTable(TableDefinition metadata) + throws TableException + { + validateTableName(metadata.getTableName()); + + if (tableExists(metadata.getTableName())) { + throw new TableException("Table '" + metadata.getTableName() + "' already exists"); + } + + try { + saveTableMetadata(metadata); + + createTableDataFile(metadata.getTableName()); + + tableMetadataCache.put(metadata.getTableName(), metadata); + tableLocks.put(metadata.getTableName(), new ReentrantReadWriteLock()); + } + catch (IOException e) { + throw new TableException("Failed to create table: " + e.getMessage()); + } + } + + public void dropTable(String tableName) + throws TableException + { + if (!tableExists(tableName)) { + throw new TableException("Table '" + tableName + "' does not exist"); + } + + ReadWriteLock lock = tableLocks.get(tableName); + lock.writeLock().lock(); + try { + deleteFile(dataDir.resolve(tableName)); + tableMetadataCache.remove(tableName); + tableLocks.remove(tableName); + } + catch (IOException e) { + throw new TableException("Failed to drop table: " + e.getMessage()); + } + finally { + lock.writeLock().unlock(); + } + } + + public void insert(String tableName, List columnNames, List values) + throws TableException + { + TableDefinition metadata = getTableMetadata(tableName); + ReadWriteLock lock = tableLocks.get(tableName); + + lock.writeLock().lock(); + try { + validateInsertData(metadata, columnNames, values); + RowDefinition row = createRow(metadata, columnNames, values); + appendRowToFile(tableName, row); + } + finally { + lock.writeLock().unlock(); + } + } + + public void batchInsert(String tableName, List columnNames, List> valuesList) + throws TableException + { + TableDefinition metadata = getTableMetadata(tableName); + ReadWriteLock lock = tableLocks.get(tableName); + + lock.writeLock().lock(); + try { + // Validate all rows first + for (List values : valuesList) { + validateInsertData(metadata, columnNames, values); + } + + // Create all rows + List rows = new ArrayList<>(); + for (List values : valuesList) { + rows.add(createRow(metadata, columnNames, values)); + } + + // Batch write to file + Path dataPath = dataDir.resolve(tableName) + .resolve("data") + .resolve("table.data"); + try (ObjectOutputStream oos = new AppendableObjectOutputStream( + Files.newOutputStream(dataPath, StandardOpenOption.APPEND))) { + for (RowDefinition row : rows) { + oos.writeObject(row); + } + } + catch (IOException e) { + throw new TableException("Failed to batch insert rows: " + e.getMessage()); + } + } + finally { + lock.writeLock().unlock(); + } + } + + public int update(String tableName, Map setValues, Condition whereCondition) + throws TableException + { + TableDefinition metadata = getTableMetadata(tableName); + ReadWriteLock lock = tableLocks.get(tableName); + + lock.writeLock().lock(); + try { + List rows = readAllRows(tableName); + int updatedCount = 0; + + for (RowDefinition row : rows) { + if (whereCondition == null || whereCondition.evaluate(row)) { +// updateRow(row, setValues); + updatedCount++; + } + } + + if (updatedCount > 0) { + saveAllRows(tableName, rows); + } + + return updatedCount; + } + finally { + lock.writeLock().unlock(); + } + } + + public int delete(String tableName, Condition whereCondition) + throws TableException + { + ReadWriteLock lock = tableLocks.get(tableName); + + lock.writeLock().lock(); + try { + List rows = readAllRows(tableName); + List remainingRows = new ArrayList<>(); + int deletedCount = 0; + + for (RowDefinition row : rows) { + if (whereCondition == null || !whereCondition.evaluate(row)) { + remainingRows.add(row); + } + else { + deletedCount++; + } + } + + if (deletedCount > 0) { + saveAllRows(tableName, remainingRows); + } + + return deletedCount; + } + finally { + lock.writeLock().unlock(); + } + } + + public List select(String tableName, List columnNames, Condition whereCondition) + throws TableException + { + TableDefinition metadata = getTableMetadata(tableName); + ReadWriteLock lock = tableLocks.get(tableName); + + if (columnNames != null && !columnNames.isEmpty()) { + for (String columnName : columnNames) { + if (metadata.getColumn(columnName) == null) { + throw new TableException("Column '" + columnName + "' does not exist"); + } + } + } + + lock.readLock().lock(); + try { + List rows = readAllRows(tableName); + List result = new ArrayList<>(); + + for (RowDefinition row : rows) { + if (whereCondition == null || whereCondition.evaluate(row)) { + if (columnNames != null && !columnNames.isEmpty()) { + RowDefinition projectedRow = projectRow(row, columnNames); + result.add(projectedRow); + } + else { + result.add(row); + } + } + } + + return result; + } + finally { + lock.readLock().unlock(); + } + } + + private void appendRowToFile(String tableName, RowDefinition row) + throws TableException + { + Path dataPath = dataDir.resolve(tableName) + .resolve("data") + .resolve("table.data"); + try (ObjectOutputStream oos = new AppendableObjectOutputStream(Files.newOutputStream(dataPath, StandardOpenOption.APPEND))) { + oos.writeObject(row); + } + catch (IOException e) { + log.error("Failed to append row to file", e); + throw new TableException("Failed to append row to file: " + e.getMessage()); + } + } + + private void saveAllRows(String tableName, List rows) + throws TableException + { + Path dataPath = Paths.get(dataDir + tableName + ".data"); + try (ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(dataPath, StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING))) { + for (RowDefinition row : rows) { + oos.writeObject(row); + } + } + catch (IOException e) { + throw new TableException("Failed to save rows to file: " + e.getMessage()); + } + } + + private List readAllRows(String tableName) + throws TableException + { + List rows = new ArrayList<>(); + Path dataPath = dataDir.resolve(tableName).resolve("data").resolve("table.data"); + + if (!Files.exists(dataPath)) { + return rows; + } + + try (AppendableObjectInputStream ois = new AppendableObjectInputStream(Files.newInputStream(dataPath))) { + while (true) { + try { + RowDefinition row = (RowDefinition) ois.readObject(); + rows.add(row); + } + catch (EOFException e) { + break; + } + } + } + catch (IOException | ClassNotFoundException e) { + log.error("Failed to read rows", e); + throw new TableException("Failed to read rows: " + e.getMessage()); + } + + return rows; + } + + private void validateTableName(String tableName) + throws TableException + { + if (tableName == null || tableName.trim().isEmpty()) { + throw new TableException("Table name cannot be empty"); + } + if (!tableName.matches("^[a-zA-Z][a-zA-Z0-9_]*$")) { + throw new TableException("Invalid table name"); + } + if (tableName.length() > 64) { + throw new TableException("Table name is too long"); + } + } + + private void validateInsertData(TableDefinition metadata, List columnNames, List values) + throws TableException + { + if (columnNames.size() != values.size()) { + throw new TableException("Column count doesn't match value count"); + } + + for (int i = 0; i < columnNames.size(); i++) { + String columnName = columnNames.get(i); + Object value = values.get(i); + ColumnDefinition column = metadata.getColumn(columnName); + + if (column == null) { + throw new TableException("Column '" + columnName + "' does not exist"); + } + + if (!isValueTypeValid(value, column.getType())) { + throw new TableException("Invalid data type for column '" + columnName + "'"); + } + } + } + + private boolean isValueTypeValid(Object value, DataType expectedType) + { + if (value == null) { + return true; + } + + switch (expectedType) { + case INTEGER: + return value instanceof Integer; + case VARCHAR: + return value instanceof String; + case BOOLEAN: + return value instanceof Boolean; + case DOUBLE: + return value instanceof Double; + default: + return false; + } + } + + private RowDefinition createRow(TableDefinition metadata, List columnNames, List values) + { + RowDefinition row = new RowDefinition(); + for (int i = 0; i < columnNames.size(); i++) { + row.setValue(columnNames.get(i), values.get(i)); + } + return row; + } + + private RowDefinition projectRow(RowDefinition originalRow, List columnNames) + { + RowDefinition projectedRow = new RowDefinition(); + for (String columnName : columnNames) { + projectedRow.setValue(columnName, originalRow.getValue(columnName)); + } + return projectedRow; + } + + private void saveTableMetadata(TableDefinition metadata) + throws IOException + { + Path metaPath = dataDir.resolve(metadata.getTableName()) + .resolve("metadata") + .resolve("table.meta"); + if (!Files.exists(metaPath)) { + Files.createDirectories(metaPath.getParent()); + } + + try (ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(metaPath))) { + oos.writeObject(metadata); + } + catch (IOException e) { + log.error("Failed to save table metadata", e); + throw new IOException("Failed to save table metadata", e); + } + } + + private void createTableDataFile(String tableName) + throws IOException + { + Path metaPath = dataDir.resolve(tableName) + .resolve("data") + .resolve("table.data"); + if (!Files.exists(metaPath)) { + Files.createDirectories(metaPath.getParent()); + Files.createFile(metaPath); + } + } + + private TableDefinition loadTableMetadata(String tableName) + throws IOException + { + Path metaPath = dataDir.resolve(tableName) + .resolve("metadata") + .resolve("table.meta"); + try (ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(metaPath))) { + return (TableDefinition) ois.readObject(); + } + catch (IOException | ClassNotFoundException e) { + log.error("Failed to load table metadata", e); + throw new IOException("Failed to load table metadata", e); + } + } + + public boolean tableExists(String tableName) + { + return tableMetadataCache.containsKey(tableName); + } + + public TableDefinition getTableMetadata(String tableName) + throws TableException + { + TableDefinition metadata = tableMetadataCache.get(tableName); + if (metadata == null) { + throw new TableException("Table '" + tableName + "' does not exist"); + } + return metadata; + } + + private void deleteFile(Path tableDir) + throws IOException + { + try (Stream stream = Files.walk(tableDir)) { + stream.sorted(Comparator.reverseOrder()) + .forEach(path -> { + try { + Files.deleteIfExists(path); + } + catch (IOException e) { + log.error("Failed to delete: {}", path, e); + } + }); + } + } +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/metadata/ColumnDefinition.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/metadata/ColumnDefinition.java new file mode 100644 index 0000000000..d59bc5c4b5 --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/metadata/ColumnDefinition.java @@ -0,0 +1,24 @@ +package io.edurt.datacap.condor.metadata; + +import io.edurt.datacap.condor.DataType; +import lombok.Getter; + +import java.io.Serializable; + +@Getter +public class ColumnDefinition + implements Serializable +{ + private String name; + private DataType type; + private boolean isPrimaryKey; + private boolean isNullable; + + public ColumnDefinition(String name, DataType type, boolean isPrimaryKey, boolean isNullable) + { + this.name = name; + this.type = type; + this.isPrimaryKey = isPrimaryKey; + this.isNullable = isNullable; + } +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/metadata/DatabaseDefinition.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/metadata/DatabaseDefinition.java new file mode 100644 index 0000000000..dc289b0012 --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/metadata/DatabaseDefinition.java @@ -0,0 +1,23 @@ +package io.edurt.datacap.condor.metadata; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.edurt.datacap.condor.manager.TableManager; +import lombok.Getter; + +import java.nio.file.Path; + +@Getter +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class DatabaseDefinition +{ + private String name; + private Path path; + private TableManager tableManager; + + public DatabaseDefinition(String name, Path path) + { + this.name = name; + this.path = path; + this.tableManager = new TableManager(path); + } +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/metadata/RowDefinition.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/metadata/RowDefinition.java new file mode 100644 index 0000000000..4a4b25d38d --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/metadata/RowDefinition.java @@ -0,0 +1,31 @@ +package io.edurt.datacap.condor.metadata; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import lombok.Setter; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class RowDefinition + implements Serializable +{ + @Setter + private Map values; + + public RowDefinition() + { + this.values = new HashMap<>(); + } + + public void setValue(String columnName, Object value) + { + values.put(columnName, value); + } + + public Object getValue(String columnName) + { + return values.get(columnName); + } +} diff --git a/core/datacap-condor/src/main/java/io/edurt/datacap/condor/metadata/TableDefinition.java b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/metadata/TableDefinition.java new file mode 100644 index 0000000000..cc5ea37285 --- /dev/null +++ b/core/datacap-condor/src/main/java/io/edurt/datacap/condor/metadata/TableDefinition.java @@ -0,0 +1,34 @@ +package io.edurt.datacap.condor.metadata; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import lombok.Getter; +import lombok.ToString; + +import java.io.Serializable; +import java.util.List; + +@Getter +@ToString +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class TableDefinition + implements Serializable +{ + private String tableName; + private List columns; + + public TableDefinition(String tableName, List columns) + { + this.tableName = tableName; + this.columns = columns; + } + + public ColumnDefinition getColumn(String columnName) + { + for (ColumnDefinition column : columns) { + if (column.getName().equals(columnName)) { + return column; + } + } + return null; + } +} diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/SQLVisitor.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/SQLVisitor.java index a21a5cc672..beba9dc66f 100644 --- a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/SQLVisitor.java +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/SQLVisitor.java @@ -1,20 +1,37 @@ package io.edurt.datacap.sql; +import com.google.common.collect.Lists; +import io.edurt.datacap.sql.node.ColumnConstraint; +import io.edurt.datacap.sql.node.ConstraintType; +import io.edurt.datacap.sql.node.DataType; import io.edurt.datacap.sql.node.Expression; +import io.edurt.datacap.sql.node.TableConstraint; +import io.edurt.datacap.sql.node.clause.ForeignKeyClause; import io.edurt.datacap.sql.node.clause.JoinClause; import io.edurt.datacap.sql.node.clause.LimitClause; +import io.edurt.datacap.sql.node.element.ColumnElement; import io.edurt.datacap.sql.node.element.OrderByElement; import io.edurt.datacap.sql.node.element.SelectElement; import io.edurt.datacap.sql.node.element.TableElement; +import io.edurt.datacap.sql.node.option.ReferenceOption; +import io.edurt.datacap.sql.node.option.TableOption; import io.edurt.datacap.sql.parser.SqlBaseBaseVisitor; import io.edurt.datacap.sql.parser.SqlBaseParser; import io.edurt.datacap.sql.processor.ExpressionProcessor; import io.edurt.datacap.sql.processor.ShowProcessor; +import io.edurt.datacap.sql.statement.CreateDatabaseStatement; +import io.edurt.datacap.sql.statement.CreateTableStatement; +import io.edurt.datacap.sql.statement.DropDatabaseStatement; +import io.edurt.datacap.sql.statement.DropTableStatement; +import io.edurt.datacap.sql.statement.InsertStatement; import io.edurt.datacap.sql.statement.SQLStatement; import io.edurt.datacap.sql.statement.SelectStatement; +import io.edurt.datacap.sql.statement.UseDatabaseStatement; +import org.antlr.v4.runtime.RuleContext; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; public class SQLVisitor extends SqlBaseBaseVisitor @@ -58,6 +75,14 @@ else if (ctx.showStatement() != null) { return null; } + @Override + public SQLStatement visitCreateDatabaseStatement(SqlBaseParser.CreateDatabaseStatementContext ctx) + { + String databaseName = ctx.databaseName().getText(); + boolean ifNotExists = ctx.EXISTS() != null; + return new CreateDatabaseStatement(databaseName, ifNotExists); + } + @Override public SQLStatement visitSelectStatement(SqlBaseParser.SelectStatementContext ctx) { @@ -131,8 +156,44 @@ else if (ctx.queryExpression() != null) { @Override public SQLStatement visitInsertStatement(SqlBaseParser.InsertStatementContext ctx) { - // TODO: Implement insert statement parsing - return null; + String tableName = ctx.tableName().getText(); + boolean orReplace = ctx.REPLACE() != null; + + // Parse column names if present + List columns = Lists.newArrayList(); + if (ctx.columnName() != null && !ctx.columnName().isEmpty()) { + columns = ctx.columnName() + .stream() + .map(RuleContext::getText) + .collect(Collectors.toList()); + } + + // Handle VALUES case + List> values = Lists.newArrayList(); + List> simpleValues = Lists.newArrayList(); + SelectStatement select = null; + + if (ctx.insertValuesConstructor() != null && !ctx.insertValuesConstructor().isEmpty()) { + for (SqlBaseParser.InsertValuesConstructorContext valueCtx : ctx.insertValuesConstructor()) { + List row = valueCtx.value() + .stream() + .map(SqlBaseParser.ValueContext::expression) + .map(this::processExpression) + .collect(Collectors.toList()); + simpleValues.add( + row.stream() + .map(Expression::getValue) + .collect(Collectors.toList()) + ); + values.add(row); + } + } + // Handle SELECT case + else if (ctx.selectStatement() != null) { + select = (SelectStatement) visitSelectStatement(ctx.selectStatement()); + } + + return new InsertStatement(tableName, orReplace, columns, values, simpleValues, select); } @Override @@ -152,10 +213,292 @@ public SQLStatement visitDeleteStatement(SqlBaseParser.DeleteStatementContext ct @Override public SQLStatement visitCreateStatement(SqlBaseParser.CreateStatementContext ctx) { - // TODO: Implement create statement parsing + if (ctx.createDatabaseStatement() != null) { + return visitCreateDatabaseStatement(ctx.createDatabaseStatement()); + } + + if (ctx.createTableStatement() != null) { + return visitCreateTableStatement(ctx.createTableStatement()); + } + return null; } + @Override + public SQLStatement visitCreateTableStatement(SqlBaseParser.CreateTableStatementContext ctx) + { + // Parse basic table information + String tableName = ctx.tableName().getText(); + boolean isTemporary = ctx.TEMP() != null || ctx.TEMPORARY() != null; + boolean ifNotExists = ctx.IF() != null && ctx.NOT() != null && ctx.EXISTS() != null; + + // Parse table elements + List elements = new ArrayList<>(); + for (SqlBaseParser.TableElementContext elementCtx : ctx.tableElement()) { + if (elementCtx.columnDefinition() != null) { + elements.add(processColumnDefinition(elementCtx.columnDefinition())); + } + else if (elementCtx.tableConstraint() != null) { + elements.add(processTableConstraint(elementCtx.tableConstraint())); + } + } + + // Parse table options + List options = new ArrayList<>(); + if (ctx.tableOptions() != null) { + for (SqlBaseParser.TableOptionContext optionCtx : ctx.tableOptions().tableOption()) { + if (optionCtx.getChildCount() >= 3 && optionCtx.getChild(1).getText().equals("=")) { + String name = null; + String value = null; + + if (optionCtx.ENGINE() != null && optionCtx.STRING() != null) { + name = "ENGINE"; + value = unquoteString(optionCtx.STRING().getText()); + } + else if (optionCtx.CHARSET() != null && optionCtx.STRING() != null) { + name = "CHARSET"; + value = unquoteString(optionCtx.STRING().getText()); + } + else if (optionCtx.COLLATE() != null && optionCtx.STRING() != null) { + name = "COLLATE"; + value = unquoteString(optionCtx.STRING().getText()); + } + else if (optionCtx.AUTO_INCREMENT() != null && optionCtx.INTEGER_VALUE() != null) { + name = "AUTO_INCREMENT"; + value = optionCtx.INTEGER_VALUE().getText(); + } + else if (optionCtx.COMMENT() != null && optionCtx.STRING() != null) { + name = "COMMENT"; + value = unquoteString(optionCtx.STRING().getText()); + } + + if (name != null && value != null) { + options.add(new TableOption(name, value)); + } + } + } + } + + return new CreateTableStatement( + tableName, + isTemporary, + ifNotExists, + elements, + options + ); + } + + private ColumnElement processColumnDefinition(SqlBaseParser.ColumnDefinitionContext ctx) + { + // Parse column name + String columnName = ctx.columnName().getText(); + + // Parse data type + DataType dataType = processDataType(ctx.dataType()); + + // Parse column constraints + List constraints = new ArrayList<>(); + for (SqlBaseParser.ColumnConstraintContext constraintCtx : ctx.columnConstraint()) { + String constraintName = constraintCtx.constraintName() != null ? + constraintCtx.constraintName().getText() : null; + + ConstraintType type; + Object value = null; + ForeignKeyClause foreignKey = null; + Expression checkExpression = null; + + if (constraintCtx.NULL() != null) { + type = constraintCtx.NOT() != null ? ConstraintType.NOT_NULL : ConstraintType.NULL; + } + else if (constraintCtx.PRIMARY() != null) { + type = ConstraintType.PRIMARY_KEY; + } + else if (constraintCtx.UNIQUE() != null) { + type = ConstraintType.UNIQUE; + } + else if (constraintCtx.DEFAULT() != null) { + type = ConstraintType.DEFAULT; + value = processDefaultValue(constraintCtx.defaultValue()); + } + else if (constraintCtx.foreignKeyClause() != null) { + type = ConstraintType.FOREIGN_KEY; + foreignKey = processForeignKeyClause(constraintCtx.foreignKeyClause()); + } + else if (constraintCtx.checkConstraint() != null) { + type = ConstraintType.CHECK; + checkExpression = processExpression(constraintCtx.checkConstraint().expression()); + } + else { + continue; // Unknown constraint type + } + + constraints.add(new ColumnConstraint(constraintName, type, value, foreignKey, checkExpression)); + } + + return new ColumnElement(columnName, dataType, constraints.toArray(new ColumnConstraint[0])); + } + + private TableConstraint processTableConstraint(SqlBaseParser.TableConstraintContext ctx) + { + String constraintName = ctx.constraintName() != null ? ctx.constraintName().getText() : null; + ConstraintType type; + String[] columns = null; + ForeignKeyClause foreignKey = null; + Expression checkExpression = null; + + if (ctx.primaryKeyConstraint() != null) { + type = ConstraintType.PRIMARY_KEY; + columns = ctx.primaryKeyConstraint().columnName().stream() + .map(RuleContext::getText) + .toArray(String[]::new); + } + else if (ctx.uniqueConstraint() != null) { + type = ConstraintType.UNIQUE; + columns = ctx.uniqueConstraint().columnName().stream() + .map(RuleContext::getText) + .toArray(String[]::new); + } + else if (ctx.foreignKeyConstraint() != null) { + type = ConstraintType.FOREIGN_KEY; + columns = ctx.foreignKeyConstraint().columnName().stream() + .map(RuleContext::getText) + .toArray(String[]::new); + foreignKey = processForeignKeyClause(ctx.foreignKeyConstraint().foreignKeyClause()); + } + else if (ctx.checkConstraint() != null) { + type = ConstraintType.CHECK; + checkExpression = processExpression(ctx.checkConstraint().expression()); + } + else { + throw new IllegalStateException("Unknown constraint type"); + } + + return new TableConstraint(constraintName, type, columns, foreignKey, checkExpression); + } + + private ForeignKeyClause processForeignKeyClause(SqlBaseParser.ForeignKeyClauseContext ctx) + { + String referencedTable = ctx.tableName().getText(); + + String[] referencedColumns = null; + if (ctx.columnName() != null && !ctx.columnName().isEmpty()) { + referencedColumns = ctx.columnName().stream() + .map(RuleContext::getText) + .toArray(String[]::new); + } + + ReferenceOption onDelete = null; + ReferenceOption onUpdate = null; + + if (ctx.DELETE() != null) { + onDelete = getReferenceOption(ctx.referenceOption(0)); + } + if (ctx.UPDATE() != null) { + onUpdate = getReferenceOption(ctx.referenceOption(1)); + } + + return new ForeignKeyClause(referencedTable, referencedColumns, onDelete, onUpdate); + } + + private DataType processDataType(SqlBaseParser.DataTypeContext ctx) + { + String baseType = normalizeDataType(ctx.baseDataType().getText()); + + Integer[] parameters = null; + if (ctx.INTEGER_VALUE() != null && !ctx.INTEGER_VALUE().isEmpty()) { + parameters = ctx.INTEGER_VALUE().stream() + .map(node -> Integer.parseInt(node.getText())) + .toArray(Integer[]::new); + } + + return new DataType(baseType, parameters); + } + + private String normalizeDataType(String baseType) + { + // Normalize case and handle type aliases + switch (baseType.toUpperCase()) { + case "INT": + case "INTEGER": + return "INTEGER"; + case "BOOL": + case "BOOLEAN": + return "BOOLEAN"; + case "DEC": + case "DECIMAL": + case "NUMERIC": + return "DECIMAL"; + case "CHAR": + case "CHARACTER": + return "CHARACTER"; + default: + return baseType.toUpperCase(); + } + } + + private ReferenceOption getReferenceOption(SqlBaseParser.ReferenceOptionContext ctx) + { + if (ctx.RESTRICT() != null) { + return ReferenceOption.RESTRICT; + } + if (ctx.CASCADE() != null) { + return ReferenceOption.CASCADE; + } + if (ctx.NULL() != null) { + return ReferenceOption.SET_NULL; + } + if (ctx.NO() != null) { + return ReferenceOption.NO_ACTION; + } + if (ctx.DEFAULT() != null) { + return ReferenceOption.SET_DEFAULT; + } + return ReferenceOption.NO_ACTION; // Default behavior + } + + private Object processDefaultValue(SqlBaseParser.DefaultValueContext ctx) + { + if (ctx.literal() != null) { + return processLiteral(ctx.literal()); + } + else if (ctx.expression() != null) { + return processExpression(ctx.expression()); + } + return null; + } + + private Object processLiteral(SqlBaseParser.LiteralContext ctx) + { + if (ctx.STRING() != null) { + return unquoteString(ctx.STRING().getText()); + } + else if (ctx.INTEGER_VALUE() != null) { + return Long.parseLong(ctx.INTEGER_VALUE().getText()); + } + else if (ctx.DECIMAL_VALUE() != null) { + return Double.parseDouble(ctx.DECIMAL_VALUE().getText()); + } + else if (ctx.TRUE() != null) { + return true; + } + else if (ctx.FALSE() != null) { + return false; + } + else if (ctx.NULL() != null) { + return null; + } + return null; + } + + private String unquoteString(String str) + { + if (str == null || str.length() < 2) { + return str; + } + // Remove surrounding quotes (either single or double quotes) + return str.substring(1, str.length() - 1); + } + @Override public SQLStatement visitAlterStatement(SqlBaseParser.AlterStatementContext ctx) { @@ -166,15 +509,37 @@ public SQLStatement visitAlterStatement(SqlBaseParser.AlterStatementContext ctx) @Override public SQLStatement visitDropStatement(SqlBaseParser.DropStatementContext ctx) { - // TODO: Implement drop statement parsing + if (ctx.dropDatabaseStatement() != null) { + return visitDropDatabaseStatement(ctx.dropDatabaseStatement()); + } + + if (ctx.dropTableStatement() != null) { + return visitDropTableStatement(ctx.dropTableStatement()); + } return null; } + @Override + public SQLStatement visitDropDatabaseStatement(SqlBaseParser.DropDatabaseStatementContext ctx) + { + boolean ifNotExists = ctx.EXISTS() != null; + return new DropDatabaseStatement(ctx.databaseName().getText(), ifNotExists); + } + + @Override + public SQLStatement visitDropTableStatement(SqlBaseParser.DropTableStatementContext ctx) + { + boolean ifNotExists = ctx.EXISTS() != null; + List tableNames = ctx.tableName().stream() + .map(RuleContext::getText) + .collect(Collectors.toList()); + return new DropTableStatement(tableNames, ifNotExists); + } + @Override public SQLStatement visitUseStatement(SqlBaseParser.UseStatementContext ctx) { - // TODO: Implement use statement parsing - return null; + return new UseDatabaseStatement(ctx.databaseName().getText()); } @Override diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/ColumnConstraint.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/ColumnConstraint.java new file mode 100644 index 0000000000..6e19866fde --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/ColumnConstraint.java @@ -0,0 +1,28 @@ +package io.edurt.datacap.sql.node; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.edurt.datacap.sql.node.clause.ForeignKeyClause; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class ColumnConstraint +{ + private final String constraintName; + private final ConstraintType type; + private final Object value; // For DEFAULT value + private final ForeignKeyClause foreignKey; + private final Expression checkExpression; + + public ColumnConstraint(String constraintName, ConstraintType type, Object value, + ForeignKeyClause foreignKey, Expression checkExpression) + { + this.constraintName = constraintName; + this.type = type; + this.value = value; + this.foreignKey = foreignKey; + this.checkExpression = checkExpression; + } +} diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/ConstraintType.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/ConstraintType.java new file mode 100644 index 0000000000..86e1cdd9c6 --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/ConstraintType.java @@ -0,0 +1,12 @@ +package io.edurt.datacap.sql.node; + +public enum ConstraintType +{ + NOT_NULL, + NULL, + PRIMARY_KEY, + UNIQUE, + DEFAULT, + FOREIGN_KEY, + CHECK +} diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/DataType.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/DataType.java new file mode 100644 index 0000000000..34d0a6f225 --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/DataType.java @@ -0,0 +1,20 @@ +package io.edurt.datacap.sql.node; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class DataType +{ + private final String baseType; + private final Integer[] parameters; // For types like VARCHAR(255) + + public DataType(String baseType, Integer[] parameters) + { + this.baseType = baseType; + this.parameters = parameters; + } +} \ No newline at end of file diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/TableConstraint.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/TableConstraint.java new file mode 100644 index 0000000000..7a27b08e71 --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/TableConstraint.java @@ -0,0 +1,30 @@ +package io.edurt.datacap.sql.node; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.edurt.datacap.sql.node.clause.ForeignKeyClause; +import io.edurt.datacap.sql.node.element.TableElement; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class TableConstraint + extends TableElement +{ + private final String constraintName; + private final ConstraintType type; + private final String[] columns; + private final ForeignKeyClause foreignKey; + private final Expression checkExpression; + + public TableConstraint(String constraintName, ConstraintType type, String[] columns, + ForeignKeyClause foreignKey, Expression checkExpression) + { + this.constraintName = constraintName; + this.type = type; + this.columns = columns; + this.foreignKey = foreignKey; + this.checkExpression = checkExpression; + } +} diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/clause/ForeignKeyClause.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/clause/ForeignKeyClause.java new file mode 100644 index 0000000000..34cd170c67 --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/clause/ForeignKeyClause.java @@ -0,0 +1,26 @@ +package io.edurt.datacap.sql.node.clause; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.edurt.datacap.sql.node.option.ReferenceOption; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class ForeignKeyClause +{ + private final String referencedTable; + private final String[] referencedColumns; + private final ReferenceOption onDelete; + private final ReferenceOption onUpdate; + + public ForeignKeyClause(String referencedTable, String[] referencedColumns, + ReferenceOption onDelete, ReferenceOption onUpdate) + { + this.referencedTable = referencedTable; + this.referencedColumns = referencedColumns; + this.onDelete = onDelete; + this.onUpdate = onUpdate; + } +} diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/element/ColumnElement.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/element/ColumnElement.java new file mode 100644 index 0000000000..bc55bf2b7f --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/element/ColumnElement.java @@ -0,0 +1,25 @@ +package io.edurt.datacap.sql.node.element; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.edurt.datacap.sql.node.ColumnConstraint; +import io.edurt.datacap.sql.node.DataType; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class ColumnElement + extends TableElement +{ + private final String columnName; + private final DataType dataType; + private final ColumnConstraint[] constraints; + + public ColumnElement(String columnName, DataType dataType, ColumnConstraint[] constraints) + { + this.columnName = columnName; + this.dataType = dataType; + this.constraints = constraints; + } +} \ No newline at end of file diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/element/SelectElement.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/element/SelectElement.java index 3ee9c46f87..13eeed759a 100644 --- a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/element/SelectElement.java +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/element/SelectElement.java @@ -4,9 +4,11 @@ import io.edurt.datacap.sql.node.Expression; import lombok.Getter; import lombok.Setter; +import lombok.ToString; @Getter @Setter +@ToString @SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) public class SelectElement { diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/option/ReferenceOption.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/option/ReferenceOption.java new file mode 100644 index 0000000000..ca103c7840 --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/option/ReferenceOption.java @@ -0,0 +1,10 @@ +package io.edurt.datacap.sql.node.option; + +public enum ReferenceOption +{ + RESTRICT, + CASCADE, + SET_NULL, + NO_ACTION, + SET_DEFAULT +} diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/option/TableOption.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/option/TableOption.java new file mode 100644 index 0000000000..34d377491e --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/node/option/TableOption.java @@ -0,0 +1,20 @@ +package io.edurt.datacap.sql.node.option; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class TableOption +{ + private final String name; + private final String value; + + public TableOption(String name, String value) + { + this.name = name; + this.value = value; + } +} diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/processor/ExpressionProcessor.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/processor/ExpressionProcessor.java index 2bc491d6e7..4e856f49ba 100644 --- a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/processor/ExpressionProcessor.java +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/processor/ExpressionProcessor.java @@ -69,7 +69,25 @@ public Expression visitLiteralPrimary(SqlBaseParser.LiteralPrimaryContext ctx) { Expression expr = new Expression(); expr.setType(Expression.ExpressionType.LITERAL); - expr.setValue(ctx.literal().getText()); + + if (ctx.literal().INTEGER_VALUE() != null) { + expr.setValue(Integer.valueOf(ctx.literal().INTEGER_VALUE().getText())); + } + else if (ctx.literal().DECIMAL_VALUE() != null) { + expr.setValue(Double.valueOf(ctx.literal().DECIMAL_VALUE().getText())); + } + else if (ctx.literal().STRING() != null) { + expr.setValue(ctx.literal().getText()); + } + else if (ctx.literal().TRUE() != null) { + expr.setValue(true); + } + else if (ctx.literal().FALSE() != null) { + expr.setValue(false); + } + else if (ctx.literal().NULL() != null) { + expr.setValue(null); + } return expr; } diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/CreateDatabaseStatement.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/CreateDatabaseStatement.java new file mode 100644 index 0000000000..5601d7e77b --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/CreateDatabaseStatement.java @@ -0,0 +1,22 @@ +package io.edurt.datacap.sql.statement; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class CreateDatabaseStatement + extends SQLStatement +{ + private final String databaseName; + private final boolean ifNotExists; + + public CreateDatabaseStatement(String databaseName, boolean ifNotExists) + { + super(StatementType.CREATE); + this.databaseName = databaseName; + this.ifNotExists = ifNotExists; + } +} diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/CreateTableStatement.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/CreateTableStatement.java new file mode 100644 index 0000000000..62fe8f74ea --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/CreateTableStatement.java @@ -0,0 +1,38 @@ +package io.edurt.datacap.sql.statement; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.edurt.datacap.sql.node.element.TableElement; +import io.edurt.datacap.sql.node.option.TableOption; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class CreateTableStatement + extends SQLStatement +{ + private final String tableName; + private final boolean temporary; + private final boolean ifNotExists; + private final List columns; + private final List options; + + public CreateTableStatement( + String tableName, + boolean temporary, + boolean ifNotExists, + List columns, + List options + ) + { + super(StatementType.CREATE); + this.tableName = tableName; + this.temporary = temporary; + this.ifNotExists = ifNotExists; + this.columns = columns; + this.options = options; + } +} diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/DropDatabaseStatement.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/DropDatabaseStatement.java new file mode 100644 index 0000000000..49c241086f --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/DropDatabaseStatement.java @@ -0,0 +1,22 @@ +package io.edurt.datacap.sql.statement; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class DropDatabaseStatement + extends SQLStatement +{ + private final String databaseName; + private final boolean ifNotExists; + + public DropDatabaseStatement(String databaseName, boolean ifNotExists) + { + super(StatementType.DROP); + this.databaseName = databaseName; + this.ifNotExists = ifNotExists; + } +} diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/DropTableStatement.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/DropTableStatement.java new file mode 100644 index 0000000000..f24b9b9b47 --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/DropTableStatement.java @@ -0,0 +1,24 @@ +package io.edurt.datacap.sql.statement; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class DropTableStatement + extends SQLStatement +{ + private final List tableNames; + private final boolean ifExists; + + public DropTableStatement(List tableNames, boolean ifExists) + { + super(StatementType.DROP); + this.tableNames = tableNames; + this.ifExists = ifExists; + } +} diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/InsertStatement.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/InsertStatement.java new file mode 100644 index 0000000000..cf6c888b79 --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/InsertStatement.java @@ -0,0 +1,42 @@ +package io.edurt.datacap.sql.statement; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.edurt.datacap.sql.node.Expression; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Setter +@ToString +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class InsertStatement + extends SQLStatement +{ + private final String tableName; + private final boolean orReplace; + private final List columns; + private final List> values; + private final List> simpleValues; + private final SelectStatement select; + + public InsertStatement( + String tableName, + boolean orReplace, + List columns, + List> values, + List> simpleValues, + SelectStatement select + ) + { + super(StatementType.INSERT); + this.tableName = tableName; + this.orReplace = orReplace; + this.columns = columns; + this.values = values; + this.simpleValues = simpleValues; + this.select = select; + } +} diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/SelectStatement.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/SelectStatement.java index 8b665db765..9b56d4c9a4 100644 --- a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/SelectStatement.java +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/SelectStatement.java @@ -8,11 +8,13 @@ import io.edurt.datacap.sql.node.element.TableElement; import lombok.Getter; import lombok.Setter; +import lombok.ToString; import java.util.List; @Getter @Setter +@ToString @SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) public class SelectStatement extends SQLStatement diff --git a/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/UseDatabaseStatement.java b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/UseDatabaseStatement.java new file mode 100644 index 0000000000..d0cc54200b --- /dev/null +++ b/core/datacap-parser/src/main/java/io/edurt/datacap/sql/statement/UseDatabaseStatement.java @@ -0,0 +1,20 @@ +package io.edurt.datacap.sql.statement; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) +public class UseDatabaseStatement + extends SQLStatement +{ + private final String databaseName; + + public UseDatabaseStatement(String databaseName) + { + super(StatementType.USE); + this.databaseName = databaseName; + } +} diff --git a/core/datacap-ui/package.json b/core/datacap-ui/package.json index c082cc53ac..cbf4a42e01 100644 --- a/core/datacap-ui/package.json +++ b/core/datacap-ui/package.json @@ -26,7 +26,6 @@ "clsx": "^2.1.0", "lodash": "^4.17.21", "md-editor-v3": "^4.12.1", - "nprogress": "^0.2.0", "uuid": "^9.0.1", "view-shadcn-ui": "2024.5.3-alpha.1734608572", "vue": "^3.4.21", @@ -43,7 +42,6 @@ "devDependencies": { "@types/lodash": "^4.17.0", "@types/node": "^20.11.26", - "@types/nprogress": "^0.2.3", "@vitejs/plugin-vue": "^5.0.4", "tippy.js": "^6.3.7", "typescript": "^5.2.2", diff --git a/core/datacap-ui/pnpm-lock.yaml b/core/datacap-ui/pnpm-lock.yaml index 238b849028..2238d90439 100644 --- a/core/datacap-ui/pnpm-lock.yaml +++ b/core/datacap-ui/pnpm-lock.yaml @@ -59,9 +59,6 @@ importers: md-editor-v3: specifier: ^4.12.1 version: 4.21.2(@codemirror/view@6.34.1)(@lezer/common@1.2.3)(vue@3.5.12(typescript@5.2.2)) - nprogress: - specifier: ^0.2.0 - version: 0.2.0 uuid: specifier: ^9.0.1 version: 9.0.1 @@ -105,9 +102,6 @@ importers: '@types/node': specifier: ^20.11.26 version: 20.17.3 - '@types/nprogress': - specifier: ^0.2.3 - version: 0.2.3 '@vitejs/plugin-vue': specifier: ^5.0.4 version: 5.1.4(vite@5.4.10(@types/node@20.17.3))(vue@3.5.12(typescript@5.2.2)) @@ -728,9 +722,6 @@ packages: '@types/node@20.17.3': resolution: {integrity: sha512-tSQrmKKatLDGnG92h40GD7FzUt0MjahaHwOME4VAFeeA/Xopayq5qLyQRy7Jg/pjgKIFBXuKcGhJo+UdYG55jQ==} - '@types/nprogress@0.2.3': - resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} - '@types/web-bluetooth@0.0.20': resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} @@ -1269,9 +1260,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nprogress@0.2.0: - resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} - p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -2309,8 +2297,6 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/nprogress@0.2.3': {} - '@types/web-bluetooth@0.0.20': {} '@vavt/util@2.1.0': {} @@ -3019,8 +3005,6 @@ snapshots: nanoid@3.3.7: {} - nprogress@0.2.0: {} - p-limit@2.3.0: dependencies: p-try: 2.2.0 diff --git a/core/datacap-ui/src/router/index.ts b/core/datacap-ui/src/router/index.ts index 527a2006f0..21e3c6ac3a 100644 --- a/core/datacap-ui/src/router/index.ts +++ b/core/datacap-ui/src/router/index.ts @@ -2,16 +2,10 @@ import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' import { createAuthRoute } from '@/router/auth' import { createDefaultRouter } from '@/router/default' import { createHttpRoute } from '@/router/http' -import NProgress from 'nprogress' -import 'nprogress/nprogress.css' +// @ts-ignore +import { LoadingBar } from 'view-shadcn-ui' -NProgress.configure({ - easing: 'ease', - speed: 600, - showSpinner: true, - trickleSpeed: 200, - minimum: 0.3 -}) +LoadingBar.enabledNetwork() const routes: Array = [] @@ -25,7 +19,7 @@ createAuthRoute(router) createDefaultRouter(router) router.beforeEach((_to, _from, _next) => { - NProgress.start() + LoadingBar.start() if (_to.matched.length === 0) { _next('/common/404') } @@ -34,6 +28,6 @@ router.beforeEach((_to, _from, _next) => { } }) -router.afterEach(() => NProgress.done()) +router.afterEach(() => LoadingBar.done()) export default router diff --git a/pom.xml b/pom.xml index fc71e9d284..c69b85c7bd 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ core/datacap-captcha core/datacap-sql core/datacap-plugin + core/datacap-condor lib/datacap-http lib/datacap-logger lib/datacap-shell @@ -112,6 +113,7 @@ test/datacap-test-fs test/datacap-test-driver test/datacap-test-parser + test/datacap-test-condor datacap diff --git a/test/datacap-test-condor/pom.xml b/test/datacap-test-condor/pom.xml new file mode 100644 index 0000000000..3e3773b5f7 --- /dev/null +++ b/test/datacap-test-condor/pom.xml @@ -0,0 +1,28 @@ + + 4.0.0 + + io.edurt.datacap + datacap + 2024.4.1-SNAPSHOT + ../../pom.xml + + + datacap-test-condor + DataCap - Test - Condor + + + + junit + junit + ${junit.version} + test + + + io.edurt.datacap + datacap-condor + ${project.version} + test + + + diff --git a/test/datacap-test-condor/src/test/java/io/edurt/datacap/condor/manager/DatabaseTest.java b/test/datacap-test-condor/src/test/java/io/edurt/datacap/condor/manager/DatabaseTest.java new file mode 100644 index 0000000000..58a8abd4bc --- /dev/null +++ b/test/datacap-test-condor/src/test/java/io/edurt/datacap/condor/manager/DatabaseTest.java @@ -0,0 +1,64 @@ +package io.edurt.datacap.condor.manager; + +import io.edurt.datacap.condor.SQLExecutor; +import io.edurt.datacap.condor.SQLResult; +import io.edurt.datacap.condor.metadata.RowDefinition; +import lombok.extern.slf4j.Slf4j; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +import java.util.List; + +import static org.junit.Assert.assertTrue; + +@Slf4j +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class DatabaseTest +{ + private final DatabaseManager databaseManager = DatabaseManager.createManager(); + + @Test + public void step1CreateDatabase() + { + SQLExecutor executor = new SQLExecutor(databaseManager); + assertTrue(executor.execute("CREATE DATABASE IF NOT EXISTS test").isSuccess()); + } + + @Test + public void step2UseDatabase() + { + SQLExecutor executor = new SQLExecutor(databaseManager); + assertTrue(executor.execute("USE DATABASE test").isSuccess()); + } + + @Test + public void step3DropDatabase() + { + SQLExecutor executor = new SQLExecutor(databaseManager); + assertTrue(executor.execute("DROP DATABASE IF EXISTS test").isSuccess()); + } + + @Test + public void step4CreateTable() + { + SQLExecutor executor = new SQLExecutor(databaseManager); + log.info("{}", executor.execute("USE test")); + + String sql = "DROP TABLE IF EXISTS test_table"; + log.info("{}", executor.execute(sql)); + + sql = "CREATE TABLE IF NOT EXISTS test_table (id INT, name VARCHAR(255))"; + log.info("{}", executor.execute(sql)); + + sql = "INSERT INTO test_table (id, name) VALUES (1, 'John')"; + log.info("{}", executor.execute(sql)); + + sql = "INSERT INTO test_table (id, name) VALUES (1, 'John'), (2, 'Jane')"; + log.info("{}", executor.execute(sql)); + + sql = "SELECT id, name FROM test_table"; + SQLResult> rows = executor.execute(sql); + rows.getData().forEach(row -> log.info("{}", row.getValue("name"))); + } +}