Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MySQL Database Connection Support #247

Merged
merged 9 commits into from
Aug 18, 2024
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
61 changes: 60 additions & 1 deletion src/Apps/NetPad.Apps.App/App/src/core/@application/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3530,6 +3530,11 @@ export abstract class DataConnection implements IDataConnection {
result.init(data);
return result;
}
if (data["discriminator"] === "MySqlDatabaseConnection") {
let result = new MySqlDatabaseConnection();
result.init(data);
return result;
}
throw new Error("The abstract class 'DataConnection' cannot be instantiated.");
}

Expand All @@ -3553,7 +3558,7 @@ export interface IDataConnection {
type: DataConnectionType;
}

export type DataConnectionType = "MSSQLServer" | "PostgreSQL" | "SQLite";
export type DataConnectionType = "MSSQLServer" | "PostgreSQL" | "SQLite" | "MySQL";

export class DataConnectionTestResult implements IDataConnectionTestResult {
success!: boolean;
Expand Down Expand Up @@ -5376,6 +5381,7 @@ export class Types implements ITypes {
msSqlServerDatabaseConnection?: MsSqlServerDatabaseConnection | undefined;
postgreSqlDatabaseConnection?: PostgreSqlDatabaseConnection | undefined;
sqLiteDatabaseConnection?: SQLiteDatabaseConnection | undefined;
mySqlDatabaseConnection?: MySqlDatabaseConnection | undefined;

constructor(data?: ITypes) {
if (data) {
Expand Down Expand Up @@ -5424,6 +5430,7 @@ export class Types implements ITypes {
this.msSqlServerDatabaseConnection = _data["msSqlServerDatabaseConnection"] ? MsSqlServerDatabaseConnection.fromJS(_data["msSqlServerDatabaseConnection"]) : <any>undefined;
this.postgreSqlDatabaseConnection = _data["postgreSqlDatabaseConnection"] ? PostgreSqlDatabaseConnection.fromJS(_data["postgreSqlDatabaseConnection"]) : <any>undefined;
this.sqLiteDatabaseConnection = _data["sqLiteDatabaseConnection"] ? SQLiteDatabaseConnection.fromJS(_data["sqLiteDatabaseConnection"]) : <any>undefined;
this.mySqlDatabaseConnection = _data["mySqlDatabaseConnection"] ? MySqlDatabaseConnection.fromJS(_data["mySqlDatabaseConnection"]) : <any>undefined;
}
}

Expand Down Expand Up @@ -5472,6 +5479,7 @@ export class Types implements ITypes {
data["msSqlServerDatabaseConnection"] = this.msSqlServerDatabaseConnection ? this.msSqlServerDatabaseConnection.toJSON() : <any>undefined;
data["postgreSqlDatabaseConnection"] = this.postgreSqlDatabaseConnection ? this.postgreSqlDatabaseConnection.toJSON() : <any>undefined;
data["sqLiteDatabaseConnection"] = this.sqLiteDatabaseConnection ? this.sqLiteDatabaseConnection.toJSON() : <any>undefined;
data["mySqlDatabaseConnection"] = this.mySqlDatabaseConnection ? this.mySqlDatabaseConnection.toJSON() : <any>undefined;
return data;
}

Expand Down Expand Up @@ -5520,6 +5528,7 @@ export interface ITypes {
msSqlServerDatabaseConnection?: MsSqlServerDatabaseConnection | undefined;
postgreSqlDatabaseConnection?: PostgreSqlDatabaseConnection | undefined;
sqLiteDatabaseConnection?: SQLiteDatabaseConnection | undefined;
mySqlDatabaseConnection?: MySqlDatabaseConnection | undefined;
}

export type YesNoCancel = "Yes" | "No" | "Cancel";
Expand Down Expand Up @@ -7380,6 +7389,11 @@ export abstract class DatabaseConnection extends DataConnection implements IData
result.init(data);
return result;
}
if (data["discriminator"] === "MySqlDatabaseConnection") {
let result = new MySqlDatabaseConnection();
result.init(data);
return result;
}
throw new Error("The abstract class 'DatabaseConnection' cannot be instantiated.");
}

Expand Down Expand Up @@ -7448,6 +7462,11 @@ export abstract class EntityFrameworkDatabaseConnection extends DatabaseConnecti
result.init(data);
return result;
}
if (data["discriminator"] === "MySqlDatabaseConnection") {
let result = new MySqlDatabaseConnection();
result.init(data);
return result;
}
throw new Error("The abstract class 'EntityFrameworkDatabaseConnection' cannot be instantiated.");
}

Expand Down Expand Up @@ -7497,6 +7516,11 @@ export abstract class EntityFrameworkRelationalDatabaseConnection extends Entity
result.init(data);
return result;
}
if (data["discriminator"] === "MySqlDatabaseConnection") {
let result = new MySqlDatabaseConnection();
result.init(data);
return result;
}
throw new Error("The abstract class 'EntityFrameworkRelationalDatabaseConnection' cannot be instantiated.");
}

Expand Down Expand Up @@ -7663,6 +7687,41 @@ export class PostgreSqlDatabaseConnection extends EntityFrameworkRelationalDatab
export interface IPostgreSqlDatabaseConnection extends IEntityFrameworkRelationalDatabaseConnection {
}

export class MySqlDatabaseConnection extends EntityFrameworkRelationalDatabaseConnection implements IMySqlDatabaseConnection {

constructor(data?: IMySqlDatabaseConnection) {
super(data);
this._discriminator = "MySqlDatabaseConnection";
}

init(_data?: any) {
super.init(_data);
}

static fromJS(data: any): MySqlDatabaseConnection {
data = typeof data === 'object' ? data : {};
let result = new MySqlDatabaseConnection();
result.init(data);
return result;
}

toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
super.toJSON(data);
return data;
}

clone(): MySqlDatabaseConnection {
const json = this.toJSON();
let result = new MySqlDatabaseConnection();
result.init(json);
return result;
}
}

export interface IMySqlDatabaseConnection extends IEntityFrameworkRelationalDatabaseConnection {
}

export class SQLiteDatabaseConnection extends EntityFrameworkRelationalDatabaseConnection implements ISQLiteDatabaseConnection {

constructor(data?: ISQLiteDatabaseConnection) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export class DataConnectionName {
return "/img/postgresql2.png";
case "SQLite":
return "/img/sqlite.png";
case "MySQL":
return "/img/mysql.png";
default:
return "";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
DataConnection,
MsSqlServerDatabaseConnection,
PostgreSqlDatabaseConnection,
SQLiteDatabaseConnection
SQLiteDatabaseConnection,
MySqlDatabaseConnection
} from "@application";
import {IDataConnectionView} from "./idata-connection-view";
import {IDataConnectionViewComponent} from "./components/idata-connection-view-component";
Expand Down Expand Up @@ -57,6 +58,8 @@ export abstract class DataConnectionView<TDataConnection extends DataConnection>
connection.type = "PostgreSQL";
} else if (ctor.name === SQLiteDatabaseConnection.name) {
connection.type = "SQLite";
} else if (ctor.name === MySqlDatabaseConnection.name) {
connection.type = "MySQL";
} else {
throw new Error("Unhandled data connection type: " + ctor.name);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<au-compose repeat.for="component of components"
component.bind="component"></au-compose>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {DataConnection, IDataConnectionService, MySqlDatabaseConnection,} from "@application";
import {HostAndPortComponent} from "../components/host-and-port-component";
import {AuthComponent} from "../components/auth-component";
import {DatabaseComponent} from "../components/database-component";
import {DataConnectionView} from "../data-connection-view";

export class MysqlView extends DataConnectionView<MySqlDatabaseConnection> {
constructor(connection: DataConnection | undefined, dataConnectionService: IDataConnectionService) {
super(MySqlDatabaseConnection, connection);

this.components = [
new HostAndPortComponent(this.connection),
new AuthComponent(this.connection, dataConnectionService),
new DatabaseComponent(
this.connection,
undefined,
{
enabled: true,
requirementsToLoadAreMet: () => this.components.slice(0, 2).every(c => !c.validationError),
dataConnectionService: dataConnectionService
}
)
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {IDataConnectionView} from "./connection-views/idata-connection-view";
import {MssqlView} from "./connection-views/mssql/mssql-view";
import {PostgresqlView} from "./connection-views/postgresql/postgresql-view";
import {SqliteView} from "./connection-views/sqlite/sqlite-view";
import {MysqlView} from "./connection-views/mysql/mysql-view";

export class Window extends WindowBase {
public connectionView?: IDataConnectionView;
Expand Down Expand Up @@ -36,6 +37,10 @@ export class Window extends WindowBase {
label: '<img src="/img/postgresql2.png" class="connection-type-logo"/> PostgreSQL',
type: "PostgreSQL"
},
{
label: '<img src="/img/mysql.png" class="connection-type-logo"/> MySQL',
type: "MySQL"
}
];

// Until we implement a way to add a SQLite file in the browser, this option will only be available in Electron app
Expand Down Expand Up @@ -152,6 +157,10 @@ export class Window extends WindowBase {
return new SqliteView(connection, this.dataConnectionService);
}

if (connectionType === "MySQL") {
return new MysqlView(connection, this.dataConnectionService);
}

return undefined;
}

Expand Down
1 change: 1 addition & 0 deletions src/Apps/NetPad.Apps.App/Controllers/TypesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,6 @@ private class Types
public MsSqlServerDatabaseConnection? MsSqlServerDatabaseConnection { get; set; }
public PostgreSqlDatabaseConnection? PostgreSqlDatabaseConnection { get; set; }
public SQLiteDatabaseConnection? SQLiteDatabaseConnection { get; set; }
public MySqlDatabaseConnection? MySqlDatabaseConnection { get; set; }
}
}
Binary file added src/Apps/NetPad.Apps.App/wwwroot/img/mysql.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Data.Common;
using Microsoft.EntityFrameworkCore;
using NetPad.Apps.Data.EntityFrameworkCore.Scaffolding;
using NetPad.Data;

namespace NetPad.Apps.Data.EntityFrameworkCore.DataConnections;

public sealed class MySqlDatabaseConnection(Guid id, string name, ScaffoldOptions? scaffoldOptions = null)
: EntityFrameworkRelationalDatabaseConnection(id, name, DataConnectionType.MySQL,
"Pomelo.EntityFrameworkCore.MySql", scaffoldOptions)
{
public override string GetConnectionString(IDataConnectionPasswordProtector passwordProtector)
{
ConnectionStringBuilder connectionStringBuilder = [];

string host = Host ?? string.Empty;

if (!string.IsNullOrWhiteSpace(Port))
{
host += $";Port={Port}";
}

connectionStringBuilder.TryAdd("Server", host);
connectionStringBuilder.TryAdd("Database", DatabaseName);

if (UserId != null)
{
connectionStringBuilder.TryAdd("Uid", UserId);
}

if (Password != null)
{
connectionStringBuilder.TryAdd("Pwd", passwordProtector.Unprotect(Password));
}

if (!string.IsNullOrWhiteSpace(ConnectionStringAugment))
{
connectionStringBuilder.Augment(new ConnectionStringBuilder(ConnectionStringAugment));
}

return connectionStringBuilder.Build();
}

public override Task ConfigureDbContextOptionsAsync(DbContextOptionsBuilder builder, IDataConnectionPasswordProtector passwordProtector)
{
var connectionString = GetConnectionString(passwordProtector);

var serverVersion = MySqlServerVersion.AutoDetect(connectionString);

builder.UseMySql(connectionString, serverVersion, options =>
{
options.EnableRetryOnFailure();
});

return Task.CompletedTask;
}

public override async Task<IEnumerable<string>> GetDatabasesAsync(IDataConnectionPasswordProtector passwordProtector)
{
await using DatabaseContext context = CreateDbContext(passwordProtector);
await using DbCommand command = context.Database.GetDbConnection().CreateCommand();

command.CommandText = "select schema_name from information_schema.schemata;";
await context.Database.OpenConnectionAsync();

await using DbDataReader result = await command.ExecuteReaderAsync();

List<string> databases = [];

while (await result.ReadAsync())
{
databases.Add((string)result["schema_name"]);
}

return databases;
}
}
Loading
Loading