Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 142 additions & 73 deletions cli/src/commands/wheels/base.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -1322,91 +1322,109 @@ component extends="wheels-cli.models.BaseCommand" excludeFromHelp=true {
return local.databases;
}

/**
* Get database connection
* This function should be added to base.cfc
*/
private struct function getDatabaseConnection(required struct dsInfo, required string dbType, string systemDatabase = "") {
local.result = {
success: false,
connection: "",
error: "",
driverClass: ""
private struct function getDatabaseConnection(
required struct dsInfo,
required string dbType
) {
local.conn = "";

// Get database configuration
local.config = getDatabaseConfig( arguments.dbType, arguments.dsInfo );

// Try each driver class
local.driverLoaded = false;

for ( local.driverClass in local.config.driverClasses ) {

// IMPORTANT:
// CommandBox CLI + Lucee cannot reliably use Class.forName()
// for SQLite, even when the driver is present on the JVM.
// SQLite JDBC (JDBC 4+) auto-registers, so touching the class
// is sufficient.
if ( arguments.dbType == "SQLite" || arguments.dbType == "SQLite3" ) {
CreateObject( "java", local.driverClass );
local.driverLoaded = true;
break;
}

// All other databases keep existing behavior
CreateObject( "java", "java.lang.Class" ).forName( local.driverClass );
local.driverLoaded = true;
break;
}

if ( !local.driverLoaded ) {
return {
success : false,
error : "No suitable JDBC driver found for #arguments.dbType#. Tried: "
& ArrayToList( local.config.driverClasses, ", " ),
connection: ""
};

try {
// Get database-specific configuration
local.dbConfig = getDatabaseConfig(arguments.dbType, arguments.dsInfo, arguments.systemDatabase);

// Build connection URL
local.url = buildJDBCUrl(local.dbConfig.tempDS);
local.username = local.dbConfig.tempDS.username ?: "";
local.password = local.dbConfig.tempDS.password ?: "";

detailOutput.output("Connecting to " & arguments.dbType & " database...");

// Try to load driver
local.driver = "";
local.driverFound = false;

for (local.driverClass in local.dbConfig.driverClasses) {
try {
local.driver = createObject("java", local.driverClass);
local.result.driverClass = local.driverClass;
local.driverFound = true;
detailOutput.statusSuccess("Driver found: " & local.driverClass);
break;
} catch (any driverError) {
// Continue trying other drivers
}

// Create properties
local.props = CreateObject( "java", "java.util.Properties" ).init();

// Common credentials
if ( Len( arguments.dsInfo.username ) ) {
local.props.setProperty( "user", arguments.dsInfo.username );
}
if ( Len( arguments.dsInfo.password ) ) {
local.props.setProperty( "password", arguments.dsInfo.password );
}

// Database-specific properties
switch ( arguments.dbType ) {

case "SQLite":
case "SQLite3":

local.props.setProperty( "busy_timeout", "5000" );
local.props.setProperty( "journal_mode", "WAL" );
local.props.setProperty( "synchronous", "NORMAL" );

// Ensure database directory exists
if (
Len( local.config.tempDS.database )
&& !FileExists( local.config.tempDS.database )
) {
local.dbDir = GetDirectoryFromPath(
local.config.tempDS.database
);

if ( Len( local.dbDir ) && !DirectoryExists( local.dbDir ) ) {
DirectoryCreate( local.dbDir, true );
}
}

if (!local.driverFound) {
local.result.error = "No " & arguments.dbType & " driver found. Ensure JDBC driver is in classpath.";
return local.result;
}

// Create properties for connection
local.props = createObject("java", "java.util.Properties");
local.props.setProperty("user", local.username);
local.props.setProperty("password", local.password);

// Test if driver accepts the URL
if (!local.driver.acceptsURL(local.url)) {
local.result.error = arguments.dbType & " driver does not accept the URL format";
return local.result;
}

// Connect using driver directly
local.conn = local.driver.connect(local.url, local.props);

if (isNull(local.conn)) {
local.result.error = "Failed to establish connection to " & arguments.dbType;
return local.result;
}

local.result.success = true;
local.result.connection = local.conn;
detailOutput.statusSuccess("Connected successfully to " & arguments.dbType & " database!");
return local.result;

} catch (any e) {
local.result.error = e.message;
if (StructKeyExists(e, "detail")) {
local.result.error &= " - " & e.detail;
}
return local.result;
}
break;
}

// Get connection (DriverManager will auto-discover SQLite driver)
local.driverManager = CreateObject( "java", "java.sql.DriverManager" );
local.conn = local.driverManager.getConnection(
local.config.jdbcUrl,
local.props
);

// Test connection
local.conn.setAutoCommit( false );

return {
success : true,
connection : local.conn,
jdbcUrl : local.config.jdbcUrl
};
}

/**
* Get database-specific configuration
* This function should also be in base.cfc
*/
private struct function getDatabaseConfig(required string dbType, required struct dsInfo, string systemDatabase = "") {
local.config = {
tempDS: Duplicate(arguments.dsInfo),
driverClasses: []
driverClasses: [],
jdbcUrl: ""
};

switch (arguments.dbType) {
Expand All @@ -1421,6 +1439,7 @@ component extends="wheels-cli.models.BaseCommand" excludeFromHelp=true {
"com.mysql.jdbc.Driver",
"org.mariadb.jdbc.Driver"
];
local.config.jdbcUrl = "jdbc:mysql://#local.config.tempDS.host#:#local.config.tempDS.port#/#local.config.tempDS.database#";
break;

case "PostgreSQL":
Expand All @@ -1433,6 +1452,7 @@ component extends="wheels-cli.models.BaseCommand" excludeFromHelp=true {
"org.postgresql.Driver",
"postgresql.Driver"
];
local.config.jdbcUrl = "jdbc:postgresql://#local.config.tempDS.host#:#local.config.tempDS.port#/#local.config.tempDS.database#";
break;

case "SQLServer":
Expand All @@ -1445,6 +1465,7 @@ component extends="wheels-cli.models.BaseCommand" excludeFromHelp=true {
local.config.driverClasses = [
"com.microsoft.sqlserver.jdbc.SQLServerDriver"
];
local.config.jdbcUrl = "jdbc:sqlserver://#local.config.tempDS.host#:#local.config.tempDS.port#;databaseName=#local.config.tempDS.database#";
break;

case "Oracle":
Expand All @@ -1458,13 +1479,61 @@ component extends="wheels-cli.models.BaseCommand" excludeFromHelp=true {
"oracle.jdbc.OracleDriver",
"oracle.jdbc.driver.OracleDriver"
];
// Build Oracle JDBC URL
if (StructKeyExists(arguments.dsInfo, "serviceName") && Len(arguments.dsInfo.serviceName)) {
local.config.jdbcUrl = "jdbc:oracle:thin:@//#local.config.tempDS.host#:#local.config.tempDS.port#/#arguments.dsInfo.serviceName#";
} else {
local.config.jdbcUrl = "jdbc:oracle:thin:@#local.config.tempDS.host#:#local.config.tempDS.port#:#local.config.tempDS.database#";
}
break;

case "H2":
// H2 doesn't need a system database
local.config.driverClasses = [
"org.h2.Driver"
];
local.config.jdbcUrl = "jdbc:h2:#local.config.tempDS.database#";
break;

case "SQLite":
case "SQLite3":
// SQLite uses file path as database
// For SQLite, the "database" field is actually a file path
if (Len(arguments.dsInfo.database)) {
local.dbPath = arguments.dsInfo.database;

// Resolve relative paths
if (!FileExists(local.dbPath)) {
// Try relative to current directory
local.appPath = getCWD();
local.resolvedPath = fileSystemUtil.resolvePath(local.appPath & "/" & local.dbPath);
if (FileExists(local.resolvedPath)) {
local.dbPath = local.resolvedPath;
}
}

local.config.tempDS.database = local.dbPath;
}

local.config.driverClasses = [
"org.sqlite.JDBC",
"org.xerial.sqlite.JDBC",
"SQLite.JDBCDriver"
];

// Build SQLite JDBC URL
if (Len(local.config.tempDS.database)) {
local.config.jdbcUrl = "jdbc:sqlite:" & local.config.tempDS.database;
} else {
local.config.jdbcUrl = "jdbc:sqlite:";
}

// SQLite-specific connection properties
local.config.connectionProperties = {
"busy_timeout": "5000",
"journal_mode": "WAL",
"synchronous": "NORMAL"
};
break;
}

Expand Down
Loading
Loading