Skip to content

Commit

Permalink
Merge pull request #250 from SimonNyvall/feat/mariadb-connection
Browse files Browse the repository at this point in the history
Add MariaDb database connection support
  • Loading branch information
tareqimbasher authored Aug 22, 2024
2 parents 679f61c + 20df89b commit b766b82
Show file tree
Hide file tree
Showing 14 changed files with 337 additions and 62 deletions.
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 @@ -3535,6 +3535,11 @@ export abstract class DataConnection implements IDataConnection {
result.init(data);
return result;
}
if (data["discriminator"] === "MariaDbDatabaseConnection") {
let result = new MariaDbDatabaseConnection();
result.init(data);
return result;
}
throw new Error("The abstract class 'DataConnection' cannot be instantiated.");
}

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

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

export class DataConnectionTestResult implements IDataConnectionTestResult {
success!: boolean;
Expand Down Expand Up @@ -5382,6 +5387,7 @@ export class Types implements ITypes {
postgreSqlDatabaseConnection?: PostgreSqlDatabaseConnection | undefined;
sqLiteDatabaseConnection?: SQLiteDatabaseConnection | undefined;
mySqlDatabaseConnection?: MySqlDatabaseConnection | undefined;
mariaDbDatabaseConnection?: MariaDbDatabaseConnection | undefined;

constructor(data?: ITypes) {
if (data) {
Expand Down Expand Up @@ -5431,6 +5437,7 @@ export class Types implements ITypes {
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;
this.mariaDbDatabaseConnection = _data["mariaDbDatabaseConnection"] ? MariaDbDatabaseConnection.fromJS(_data["mariaDbDatabaseConnection"]) : <any>undefined;
}
}

Expand Down Expand Up @@ -5480,6 +5487,7 @@ export class Types implements ITypes {
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;
data["mariaDbDatabaseConnection"] = this.mariaDbDatabaseConnection ? this.mariaDbDatabaseConnection.toJSON() : <any>undefined;
return data;
}

Expand Down Expand Up @@ -5529,6 +5537,7 @@ export interface ITypes {
postgreSqlDatabaseConnection?: PostgreSqlDatabaseConnection | undefined;
sqLiteDatabaseConnection?: SQLiteDatabaseConnection | undefined;
mySqlDatabaseConnection?: MySqlDatabaseConnection | undefined;
mariaDbDatabaseConnection?: MariaDbDatabaseConnection | undefined;
}

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

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

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

Expand Down Expand Up @@ -7722,6 +7746,41 @@ export class MySqlDatabaseConnection extends EntityFrameworkRelationalDatabaseCo
export interface IMySqlDatabaseConnection extends IEntityFrameworkRelationalDatabaseConnection {
}

export class MariaDbDatabaseConnection extends EntityFrameworkRelationalDatabaseConnection implements IMariaDbDatabaseConnection {

constructor(data?: IMariaDbDatabaseConnection ) {
super(data);
this._discriminator = "MariaDbDatabaseConnection";
}

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

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

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

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

export interface IMariaDbDatabaseConnection 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 @@ -14,6 +14,8 @@ export class DataConnectionName {
return "/img/sqlite.png";
case "MySQL":
return "/img/mysql.png";
case "MariaDB":
return "/img/mariadb.png";
default:
return "";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
MsSqlServerDatabaseConnection,
PostgreSqlDatabaseConnection,
SQLiteDatabaseConnection,
MySqlDatabaseConnection
MySqlDatabaseConnection,
MariaDbDatabaseConnection
} from "@application";
import {IDataConnectionView} from "./idata-connection-view";
import {IDataConnectionViewComponent} from "./components/idata-connection-view-component";
Expand Down Expand Up @@ -60,6 +61,8 @@ export abstract class DataConnectionView<TDataConnection extends DataConnection>
connection.type = "SQLite";
} else if (ctor.name === MySqlDatabaseConnection.name) {
connection.type = "MySQL";
} else if (ctor.name === MariaDbDatabaseConnection.name) {
connection.type = "MariaDB";
} 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, MariaDbDatabaseConnection,} 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 MariaDbView extends DataConnectionView<MariaDbDatabaseConnection> {
constructor(connection: DataConnection | undefined, dataConnectionService: IDataConnectionService) {
super(MariaDbDatabaseConnection, 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 @@ -7,6 +7,7 @@ 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";
import {MariaDbView} from "./connection-views/mariadb/mariadb-view";

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

Expand Down Expand Up @@ -161,6 +166,10 @@ export class Window extends WindowBase {
return new MysqlView(connection, this.dataConnectionService);
}

if (connectionType === "MariaDB") {
return new MariaDbView(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 @@ -65,5 +65,6 @@ private class Types
public PostgreSqlDatabaseConnection? PostgreSqlDatabaseConnection { get; set; }
public SQLiteDatabaseConnection? SQLiteDatabaseConnection { get; set; }
public MySqlDatabaseConnection? MySqlDatabaseConnection { get; set; }
public MariaDbDatabaseConnection? MariaDbDatabaseConnection { get; set; }
}
}
Binary file added src/Apps/NetPad.Apps.App/wwwroot/img/mariadb.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,35 @@
using Microsoft.EntityFrameworkCore;
using NetPad.Apps.Data.EntityFrameworkCore.Scaffolding;
using NetPad.Data;

namespace NetPad.Apps.Data.EntityFrameworkCore.DataConnections;

public sealed class MariaDbDatabaseConnection : EntityFrameworkRelationalDatabaseConnection
{
private readonly PomeloDatabaseConnection _pomeloDatabaseConnection;

public MariaDbDatabaseConnection(Guid id, string name, ScaffoldOptions? scaffoldOptions = null)
: base(id, name, DataConnectionType.MariaDB, "Pomelo.EntityFrameworkCore.MySql", scaffoldOptions)
{
_pomeloDatabaseConnection = new(() => (
Host,
Port,
DatabaseName,
UserId,
Password,
ConnectionStringAugment));
}

public override string GetConnectionString(IDataConnectionPasswordProtector passwordProtector) =>
_pomeloDatabaseConnection.GetConnectionString(passwordProtector);

public override async Task ConfigureDbContextOptionsAsync(DbContextOptionsBuilder builder, IDataConnectionPasswordProtector passwordProtector) =>
await _pomeloDatabaseConnection.ConfigureDbContextOptionsAsync(builder, passwordProtector);

public override async Task<IEnumerable<string>> GetDatabasesAsync(IDataConnectionPasswordProtector passwordProtector)
{
await using DatabaseContext context = CreateDbContext(passwordProtector);

return await _pomeloDatabaseConnection.GetDatabasesAsync(passwordProtector, context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System.Security.Cryptography;
using System.Text;
using NetPad.Application;
using NetPad.Data;

namespace NetPad.Apps.Data.EntityFrameworkCore.DataConnections;

internal class MariaDbDatabaseSchemaChangeDetectionStrategy(
IDataConnectionResourcesRepository dataConnectionResourcesRepository,
IDataConnectionPasswordProtector passwordProtector)
: EntityFrameworkSchemaChangeDetectionStrategyBase(dataConnectionResourcesRepository, passwordProtector),
IDataConnectionSchemaChangeDetectionStrategy
{
public bool CanSupport(DataConnection dataConnection)
{
return dataConnection is MariaDbDatabaseConnection;
}

public async Task<bool?> DidSchemaChangeAsync(DataConnection dataConnection)
{
if (dataConnection is not MariaDbDatabaseConnection connection)
{
return null;
}

var schemaCompareInfo = await _dataConnectionResourcesRepository.GetSchemaCompareInfoAsync<MariaDbSchemaCompareInfo>(connection.Id);

if (schemaCompareInfo == null)
{
return null;
}

if (schemaCompareInfo.GeneratedUsingStaleAppVersion())
{
return true;
}

var hash = await GetSchemaHashAsync(connection);

if (hash == null)
{
return null;
}

return hash != schemaCompareInfo.SchemaHash;
}

public async Task<SchemaCompareInfo?> GenerateSchemaCompareInfoAsync(DataConnection dataConnection)
{
if (dataConnection is not MariaDbDatabaseConnection connection)
{
return null;
}

var hash = await GetSchemaHashAsync(connection);

return hash == null ? null : new MariaDbSchemaCompareInfo(hash)
{
GeneratedOnAppVersion = AppIdentifier.PRODUCT_VERSION
};
}

private async Task<string?> GetSchemaHashAsync(MariaDbDatabaseConnection connection)
{
string[] interestingColumns = [ "table_schema", "table_name", "column_name", "is_nullable", "data_type" ];

var sql = $"""
SELECT {string.Join(",", interestingColumns)}
FROM information_schema.columns
WHERE table_schema NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
ORDER BY table_schema, table_name, column_name;
""";

StringBuilder sb = new();

await ExecuteSqlCommandAsync(connection, sql, async result =>
{
while (await result.ReadAsync())
{
foreach (var column in interestingColumns)
{
var value = result[column] as string;
sb.Append(value);
}
}
});

if (sb.Length == 0)
{
return null;
}

using var md5 = MD5.Create();
var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString()));

return Convert.ToBase64String(hash);
}

private class MariaDbSchemaCompareInfo(string schemaHash) : SchemaCompareInfo(DateTime.UtcNow)
{
public string? SchemaHash { get; } = schemaHash;
}
}
Loading

0 comments on commit b766b82

Please sign in to comment.