From 405762504dbd88e6ee32ea697275d4a9061a8381 Mon Sep 17 00:00:00 2001 From: Bob Farrell Date: Fri, 1 Nov 2024 13:47:05 +0000 Subject: [PATCH] WIP --- build.zig | 65 +++++++++++++++-------------- cli/commands/database.zig | 8 +++- cli/commands/database/create.zig | 2 +- cli/commands/database/drop.zig | 9 ++-- cli/commands/database/migrate.zig | 2 +- cli/commands/database/rollback.zig | 6 +-- cli/commands/database/schema.zig | 35 ++++++++++++++++ src/commands/database.zig | 67 +++++++++++++++++++++--------- src/compile_static_routes.zig | 2 +- src/jetzig.zig | 3 +- 10 files changed, 136 insertions(+), 63 deletions(-) create mode 100644 cli/commands/database/schema.zig diff --git a/build.zig b/build.zig index 023039b..827d7e9 100644 --- a/build.zig +++ b/build.zig @@ -142,6 +142,14 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn .target = target, }, ); + + const jetquery_dep = jetzig_dep.builder.dependency("jetquery", .{ + .target = target, + .optimize = optimize, + .jetquery_migrations_path = @as([]const u8, "src/app/database/migrations"), + .jetquery_config_path = @as([]const u8, "config/database.zig"), + }); + const jetzig_module = jetzig_dep.module("jetzig"); const zmpl_module = jetzig_dep.module("zmpl"); const zmd_module = jetzig_dep.module("zmd"); @@ -149,6 +157,7 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn const jetquery_module = jetzig_dep.module("jetquery"); const jetcommon_module = jetzig_dep.module("jetcommon"); const jetquery_migrate_module = jetzig_dep.module("jetquery_migrate"); + const jetquery_reflect_module = jetquery_dep.module("jetquery_reflect"); const build_options = b.addOptions(); build_options.addOption(Environment, "environment", environment); @@ -316,41 +325,14 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn .optimize = optimize, }); exe_database.root_module.addImport("jetquery", jetquery_module); + exe_database.root_module.addImport("jetzig", jetzig_module); exe_database.root_module.addImport("jetcommon", jetcommon_module); exe_database.root_module.addImport("jetquery_migrate", jetquery_migrate_module); - exe_database.root_module.addOptions("build_options", build_options); + exe_database.root_module.addImport("jetquery_reflect", jetquery_reflect_module); + exe_database.root_module.addImport("Schema", schema_module); + // exe_database.root_module.addOptions("build_options", build_options); - const database_migrate_step = b.step( - "jetzig:database:migrate", - "Migrate your Jetzig app's database.", - ); - const run_database_migrate_cmd = b.addRunArtifact(exe_database); - run_database_migrate_cmd.addArg("migrate"); - database_migrate_step.dependOn(&run_database_migrate_cmd.step); - - const database_rollback_step = b.step( - "jetzig:database:rollback", - "Roll back a migration in your Jetzig app's database.", - ); - const run_database_rollback_cmd = b.addRunArtifact(exe_database); - run_database_rollback_cmd.addArg("rollback"); - database_rollback_step.dependOn(&run_database_rollback_cmd.step); - - const database_create_step = b.step( - "jetzig:database:create", - "Create a database for your Jetzig app.", - ); - const run_database_create_cmd = b.addRunArtifact(exe_database); - run_database_create_cmd.addArg("create"); - database_create_step.dependOn(&run_database_create_cmd.step); - - const database_drop_step = b.step( - "jetzig:database:drop", - "Drop your Jetzig app's database.", - ); - const run_database_drop_cmd = b.addRunArtifact(exe_database); - run_database_drop_cmd.addArg("drop"); - database_drop_step.dependOn(&run_database_drop_cmd.step); + registerDatabaseSteps(b, exe_database); exe_routes.root_module.addImport("jetzig", jetzig_module); exe_routes.root_module.addImport("routes", routes_module); @@ -359,6 +341,25 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn routes_step.dependOn(&run_routes_cmd.step); } +fn registerDatabaseSteps(b: *std.Build, exe_database: *std.Build.Step.Compile) void { + const commands = .{ + .{ "migrate", "Migrate your Jetzig app's database." }, + .{ "rollback", "Roll back a migration in your Jetzig app's database." }, + .{ "create", "Create a database for your Jetzig app." }, + .{ "drop", "Drop your Jetzig app's database." }, + .{ "schema", "Read your app's database and generate a JetQuery schema." }, + }; + + inline for (commands) |command| { + const action = command[0]; + const description = command[1]; + const step = b.step("jetzig:database:" ++ action, description); + const run_cmd = b.addRunArtifact(exe_database); + run_cmd.addArg(action); + step.dependOn(&run_cmd.step); + } +} + fn generateMarkdownFragments(b: *std.Build) ![]const u8 { const file = std.fs.cwd().openFile(b.pathJoin(&.{ "src", "main.zig" }), .{}) catch |err| { switch (err) { diff --git a/cli/commands/database.zig b/cli/commands/database.zig index bd6c7ba..318f520 100644 --- a/cli/commands/database.zig +++ b/cli/commands/database.zig @@ -10,11 +10,13 @@ const migrate = @import("database/migrate.zig"); const rollback = @import("database/rollback.zig"); const create = @import("database/create.zig"); const drop = @import("database/drop.zig"); +const schema = @import("database/schema.zig"); +pub const confirm_drop_env = "JETZIG_DROP_PRODUCTION_DATABASE"; /// Command line options for the `database` command. pub const Options = struct { pub const meta = .{ - .usage_summary = "[migrate|rollback|create|drop]", + .usage_summary = "[migrate|rollback|create|drop|schema]", .full_text = \\Manage the application's database. \\ @@ -38,12 +40,13 @@ pub fn run( defer arena.deinit(); const alloc = arena.allocator(); - const Action = enum { migrate, rollback, create, drop }; + const Action = enum { migrate, rollback, create, drop, schema }; const map = std.StaticStringMap(Action).initComptime(.{ .{ "migrate", .migrate }, .{ "rollback", .rollback }, .{ "create", .create }, .{ "drop", .drop }, + .{ "schema", .schema }, }); const action = if (main_options.positionals.len > 0) @@ -71,6 +74,7 @@ pub fn run( .rollback => rollback.run(alloc, cwd, sub_args, options, T, main_options), .create => create.run(alloc, cwd, sub_args, options, T, main_options), .drop => drop.run(alloc, cwd, sub_args, options, T, main_options), + .schema => schema.run(alloc, cwd, sub_args, options, T, main_options), }; }; } diff --git a/cli/commands/database/create.zig b/cli/commands/database/create.zig index 6cd8a96..47f470d 100644 --- a/cli/commands/database/create.zig +++ b/cli/commands/database/create.zig @@ -20,7 +20,7 @@ pub fn run( \\Example: \\ \\ jetzig database create - \\ jetzig --environment testing database create + \\ jetzig --environment=testing database create \\ , .{}); diff --git a/cli/commands/database/drop.zig b/cli/commands/database/drop.zig index 60f1831..0be8e93 100644 --- a/cli/commands/database/drop.zig +++ b/cli/commands/database/drop.zig @@ -15,14 +15,17 @@ pub fn run( _ = options; if (main_options.options.help or args.len != 0) { std.debug.print( - \\Run database migrations. + \\Drop database. \\ \\Example: \\ \\ jetzig database migrate - \\ jetzig --environment testing database migrate + \\ jetzig --environment=testing database migrate \\ - , .{}); + \\To drop a production database, set the environment variable `{s}` to the name of the database you want to drop, e.g.: + \\ + \\ {0s}=my_production_production jetzig --environment=production database drop + , .{cli.database.confirm_drop_env}); return if (main_options.options.help) {} else error.JetzigCommandError; } diff --git a/cli/commands/database/migrate.zig b/cli/commands/database/migrate.zig index 94b3d6d..a30165b 100644 --- a/cli/commands/database/migrate.zig +++ b/cli/commands/database/migrate.zig @@ -20,7 +20,7 @@ pub fn run( \\Example: \\ \\ jetzig database migrate - \\ jetzig --environment testing database migrate + \\ jetzig --environment=testing database migrate \\ , .{}); diff --git a/cli/commands/database/rollback.zig b/cli/commands/database/rollback.zig index 9de4590..92e7347 100644 --- a/cli/commands/database/rollback.zig +++ b/cli/commands/database/rollback.zig @@ -15,12 +15,12 @@ pub fn run( _ = options; if (main_options.options.help or args.len != 0) { std.debug.print( - \\Run database migrations. + \\Roll back a database migration. \\ \\Example: \\ - \\ jetzig database migrate - \\ jetzig --environment testing database migrate + \\ jetzig database rollback + \\ jetzig --environment=testing database rollback \\ , .{}); diff --git a/cli/commands/database/schema.zig b/cli/commands/database/schema.zig new file mode 100644 index 0000000..989d3b6 --- /dev/null +++ b/cli/commands/database/schema.zig @@ -0,0 +1,35 @@ +const std = @import("std"); + +const cli = @import("../../cli.zig"); +const util = @import("../../util.zig"); + +pub fn run( + allocator: std.mem.Allocator, + cwd: std.fs.Dir, + args: []const []const u8, + options: cli.database.Options, + T: type, + main_options: T, +) !void { + _ = cwd; + _ = options; + if (main_options.options.help or args.len != 0) { + std.debug.print( + \\Generate a JetQuery schema file and save to `src/app/database/Schema.zig`. + \\ + \\Example: + \\ + \\ jetzig database schema + \\ + , .{}); + + return if (main_options.options.help) {} else error.JetzigCommandError; + } + + try util.execCommand(allocator, &.{ + "zig", + "build", + util.environmentBuildOption(main_options.options.environment), + "jetzig:database:schema", + }); +} diff --git a/src/commands/database.zig b/src/commands/database.zig index 50c8b86..28a6ff4 100644 --- a/src/commands/database.zig +++ b/src/commands/database.zig @@ -3,12 +3,18 @@ const std = @import("std"); const build_options = @import("build_options"); const jetquery = @import("jetquery"); +const jetzig = @import("jetzig"); const Migrate = @import("jetquery_migrate").Migrate; const MigrateSchema = @import("jetquery_migrate").MigrateSchema; +const Schema = @import("Schema"); const confirm_drop_env = "JETZIG_DROP_PRODUCTION_DATABASE"; const production_drop_failure_message = "To drop a production database, " ++ - "set `JETZIG_DROP_PRODUCTION_DATABASE={s}`. Exiting."; + "set `" ++ confirm_drop_env ++ "={s}`. Exiting."; + +const environment = jetzig.build_options.environment; +const config = @field(jetquery.config.database, @tagName(environment)); +const Action = enum { migrate, rollback, create, drop, schema }; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; @@ -24,40 +30,29 @@ pub fn main() !void { if (args.len < 2) return error.JetzigMissingDatabaseArgument; - const Action = enum { migrate, rollback, create, drop }; const map = std.StaticStringMap(Action).initComptime(.{ .{ "migrate", .migrate }, .{ "rollback", .rollback }, .{ "create", .create }, .{ "drop", .drop }, + .{ "schema", .schema }, }); const action = map.get(args[1]) orelse return error.JetzigUnrecognizedDatabaseArgument; - const environment = build_options.environment; - const config = @field(jetquery.config.database, @tagName(environment)); - - const Repo = jetquery.Repo(config.adapter, MigrateSchema); - var repo = try Repo.loadConfig( - allocator, - std.enums.nameCast(jetquery.Environment, environment), - .{ - .admin = switch (action) { - .migrate, .rollback => false, - .create, .drop => true, - }, - .context = .migration, - }, - ); - defer repo.deinit(); - switch (action) { .migrate => { + var repo = try migrationsRepo(action, allocator); + defer repo.deinit(); try Migrate(config.adapter).init(&repo).migrate(); }, .rollback => { + var repo = try migrationsRepo(action, allocator); + defer repo.deinit(); try Migrate(config.adapter).init(&repo).rollback(); }, .create => { + var repo = try migrationsRepo(action, allocator); + defer repo.deinit(); try repo.createDatabase(config.database, .{}); }, .drop => { @@ -72,14 +67,48 @@ pub fn main() !void { } }; if (std.mem.eql(u8, confirm, config.database)) { + var repo = try migrationsRepo(action, allocator); + defer repo.deinit(); try repo.dropDatabase(config.database, .{}); } else { std.log.err(production_drop_failure_message, .{config.database}); std.process.exit(1); } } else { + var repo = try migrationsRepo(action, allocator); + defer repo.deinit(); try repo.dropDatabase(config.database, .{}); } }, + .schema => { + const Repo = jetquery.Repo(config.adapter, Schema); + var repo = try Repo.loadConfig( + allocator, + std.enums.nameCast(jetquery.Environment, environment), + .{ .context = .migration }, + ); + const reflect = @import("jetquery_reflect").Reflect(config.adapter, Schema).init( + allocator, + &repo, + ); + const schema = try reflect.generateSchema(); + std.debug.print("{s}\n", .{schema}); + }, } } + +const MigrationsRepo = jetquery.Repo(config.adapter, MigrateSchema); +fn migrationsRepo(action: Action, allocator: std.mem.Allocator) !MigrationsRepo { + return try MigrationsRepo.loadConfig( + allocator, + std.enums.nameCast(jetquery.Environment, environment), + .{ + .admin = switch (action) { + .migrate, .rollback => false, + .create, .drop => true, + .schema => undefined, // We use a separate repo for schema reflection. + }, + .context = .migration, + }, + ); +} diff --git a/src/compile_static_routes.zig b/src/compile_static_routes.zig index cbbbca4..e451de8 100644 --- a/src/compile_static_routes.zig +++ b/src/compile_static_routes.zig @@ -11,7 +11,7 @@ else pub const database_lazy_connect = true; pub const jetzig_options = struct { - pub const Schema = @import("Schema"); + pub const Schema = @import("jetquery_schema"); }; pub fn main() !void { diff --git a/src/jetzig.zig b/src/jetzig.zig index 2296219..32f4b57 100644 --- a/src/jetzig.zig +++ b/src/jetzig.zig @@ -26,7 +26,8 @@ pub const DateTime = jetcommon.types.DateTime; pub const Time = jetcommon.types.Time; pub const Date = jetcommon.types.Date; -pub const environment = @import("build_options").environment; +pub const build_options = @import("build_options"); +pub const environment = build_options.environment; /// The primary interface for a Jetzig application. Create an `App` in your application's /// `src/main.zig` and call `start` to launch the application.