From 61ccfb7f756ab6bf95bb638e14b27adc3f1ec208 Mon Sep 17 00:00:00 2001 From: szelbi Date: Thu, 2 Oct 2025 20:55:05 +0200 Subject: [PATCH 01/20] switch sqlc config from yaml to json --- gleam.toml | 2 +- manifest.toml | 2 +- src/parrot.gleam | 20 ++-- src/parrot/internal/cli.gleam | 21 ++-- src/parrot/internal/sqlc.gleam | 49 +++++----- src/parrot/internal/sqlc_config.gleam | 132 ++++++++++++++++++++++++++ 6 files changed, 177 insertions(+), 49 deletions(-) create mode 100644 src/parrot/internal/sqlc_config.gleam diff --git a/gleam.toml b/gleam.toml index 224b828..04fdb5c 100644 --- a/gleam.toml +++ b/gleam.toml @@ -19,7 +19,7 @@ simplifile = ">= 2.2.0 and < 3.0.0" filepath = ">= 1.1.0 and < 2.0.0" exception = ">= 2.0.0 and < 3.0.0" argv = ">= 1.0.2 and < 2.0.0" -gleam_json = ">= 3.0.1 and < 4.0.0" +gleam_json = ">= 3.0.2 and < 4.0.0" gleam_time = ">= 1.2.0 and < 2.0.0" tom = ">= 2.0.0 and < 3.0.0" envoy = ">= 1.0.2 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml index 954f17d..fadae55 100644 --- a/manifest.toml +++ b/manifest.toml @@ -35,7 +35,7 @@ given = { version = ">= 6.0.0 and < 7.0.0" } gleam_crypto = { version = ">= 1.5.1 and < 2.0.0" } gleam_http = { version = ">= 4.0.0 and < 5.0.0" } gleam_httpc = { version = ">= 5.0.0 and < 6.0.0" } -gleam_json = { version = ">= 3.0.1 and < 4.0.0" } +gleam_json = { version = ">= 3.0.2 and < 4.0.0" } gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" } gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } gleam_time = { version = ">= 1.2.0 and < 2.0.0" } diff --git a/src/parrot.gleam b/src/parrot.gleam index 9e32dc4..8aa141f 100644 --- a/src/parrot.gleam +++ b/src/parrot.gleam @@ -17,6 +17,7 @@ import parrot/internal/project import parrot/internal/shellout import parrot/internal/spinner import parrot/internal/sqlc +import parrot/internal/sqlc_config import simplifile pub fn main() { @@ -34,7 +35,7 @@ pub fn main() { |> result.map(fn(a) { cli.Generate(a.0, a.1) }) } ["--sqlite", file_path] -> { - Ok(cli.Generate(cli.SQlite, file_path)) + Ok(cli.Generate(sqlc_config.SQLite, file_path)) } ["help"] -> Ok(cli.Usage) _ -> Ok(cli.Usage) @@ -57,7 +58,10 @@ pub fn main() { } } -fn cmd_gen(engine: cli.Engine, db: String) -> Result(Nil, errors.ParrotError) { +fn cmd_gen( + engine: sqlc_config.Engine, + db: String, +) -> Result(Nil, errors.ParrotError) { let db = case db { "sqlite://" <> db -> db "sqlite:" <> db -> db @@ -83,7 +87,7 @@ fn cmd_gen(engine: cli.Engine, db: String) -> Result(Nil, errors.ParrotError) { let sqlc_binary = sqlc.sqlc_binary_path() let sqlc_dir = filepath.directory_name(sqlc_binary) let schema_file = filepath.join(sqlc_dir, "schema.sql") - let sqlc_file = filepath.join(sqlc_dir, "sqlc.yaml") + let sqlc_file = filepath.join(sqlc_dir, "sqlc.json") let queries_file = filepath.join(sqlc_dir, "queries.json") let _ = simplifile.create_directory_all(sqlc_dir) @@ -105,26 +109,26 @@ fn cmd_gen(engine: cli.Engine, db: String) -> Result(Nil, errors.ParrotError) { Ok(_) -> spinner.complete_current(spinner, spinner.green_checkmark()) } - let sqlc_yaml = sqlc.gen_sqlc_yaml(engine, queries) - let _ = simplifile.write(sqlc_file, sqlc_yaml) + let sqlc_json = sqlc.gen_sqlc_json(engine, queries) + let _ = simplifile.write(sqlc_file, sqlc_json) let spinner = spinner.new("fetching schema") |> spinner.start() use schema_sql <- result.try(case engine { - cli.MySQL -> { + sqlc_config.MySQL -> { use schema <- result.try(db.fetch_schema_mysql(db)) Ok(schema) } - cli.PostgreSQL -> { + sqlc_config.PostgreSQL -> { use schema <- result.try(db.fetch_schema_postgresql(db)) let assert Ok(re) = regexp.from_string("(?m)^\\\\restrict.*\n|^\\\\unrestrict.*\n") let schema = regexp.replace(re, schema, "") Ok(schema) } - cli.SQlite -> { + sqlc_config.SQLite -> { use schema <- result.try(db.fetch_schema_sqlite(db)) let sql = string.trim(schema) Ok(sql) diff --git a/src/parrot/internal/cli.gleam b/src/parrot/internal/cli.gleam index 142ed62..2d9ca39 100644 --- a/src/parrot/internal/cli.gleam +++ b/src/parrot/internal/cli.gleam @@ -1,6 +1,7 @@ import envoy import given import parrot/internal/errors +import parrot/internal/sqlc_config pub const usage = " 🦜 Parrot - type-safe SQL in gleam for sqlite, postgresql & mysql @@ -54,27 +55,19 @@ pub const usage = " pub type Command { Usage - Generate(engine: Engine, db: String) -} - -pub type Engine { - SQlite - MySQL - PostgreSQL + Generate(engine: sqlc_config.Engine, db: String) } pub fn engine_from_env(str: String) { case str { - "postgres" <> _ -> Ok(PostgreSQL) - "mysql" <> _ -> Ok(MySQL) - "file" | "sqlite" <> _ -> Ok(SQlite) - _ -> { - Error(errors.UnknownEngine(str)) - } + "postgres" <> _ -> Ok(sqlc_config.PostgreSQL) + "mysql" <> _ -> Ok(sqlc_config.MySQL) + "file" | "sqlite" <> _ -> Ok(sqlc_config.SQLite) + _ -> Error(errors.UnknownEngine(str)) } } -pub fn parse_env(env: String) -> Result(#(Engine, String), String) { +pub fn parse_env(env: String) -> Result(#(sqlc_config.Engine, String), String) { let env_result = envoy.get(env) use env_var <- given.error(in: env_result, return: fn(_) { Error("Environment Variable \"DATABASE_URL\" is empty!") diff --git a/src/parrot/internal/sqlc.gleam b/src/parrot/internal/sqlc.gleam index 5b63d1c..642e597 100644 --- a/src/parrot/internal/sqlc.gleam +++ b/src/parrot/internal/sqlc.gleam @@ -7,14 +7,14 @@ import gleam/bit_array import gleam/crypto import gleam/dynamic import gleam/dynamic/decode -import gleam/option.{type Option} +import gleam/option.{type Option, Some} import gleam/result import gleam/set import gleam/string -import parrot/internal/cli import parrot/internal/errors import parrot/internal/project import parrot/internal/shellout +import parrot/internal/sqlc_config import simplifile.{Execute, FilePermissions, Read, Write} pub type TypeRef { @@ -266,29 +266,28 @@ pub fn decode_sqlc(data: dynamic.Dynamic) { decode.run(data, decoder) } -pub fn gen_sqlc_yaml(engine: cli.Engine, queries: List(String)) { - let result = " -version: \"2\" -sql: - - schema: schema.sql - queries: [" <> string.join(queries, ", ") <> "] - engine: " <> engine_to_sqlc_string(engine) <> " - gen: - json: - out: . - indent: \" \" - filename: queries.json - " - - string.trim(result) -} - -fn engine_to_sqlc_string(engine: cli.Engine) { - case engine { - cli.MySQL -> "mysql" - cli.PostgreSQL -> "postgresql" - cli.SQlite -> "sqlite" - } +pub fn gen_sqlc_json( + engine: sqlc_config.Engine, + queries: List(String), +) -> String { + let config = + sqlc_config.Config(version: sqlc_config.version_2, sql: [ + sqlc_config.Sql( + schema: Some("schema.sql"), + queries: Some(sqlc_config.Queries(queries)), + engine:, + gen: Some( + sqlc_config.Gen( + json: Some(sqlc_config.GenJson( + out: Some("."), + indent: Some(" "), + filename: Some("queries.json"), + )), + ), + ), + ), + ]) + sqlc_config.config_to_json_string(config) } pub fn sqlc_binary_path() { diff --git a/src/parrot/internal/sqlc_config.gleam b/src/parrot/internal/sqlc_config.gleam new file mode 100644 index 0000000..7d97c09 --- /dev/null +++ b/src/parrot/internal/sqlc_config.gleam @@ -0,0 +1,132 @@ +import gleam/json +import gleam/option.{type Option} + +pub type Queries { + Query(String) + Queries(List(String)) +} + +fn queries_to_json(queries: Queries) -> json.Json { + case queries { + Query(query) -> + json.object([ + #("type", json.string("query")), + #("query", json.string(query)), + ]) + Queries(queries) -> + json.object([ + #("type", json.string("queries")), + #("queries", json.array(queries, json.string)), + ]) + } +} + +pub type Engine { + SQLite + MySQL + PostgreSQL +} + +fn engine_to_json(engine: Engine) -> json.Json { + case engine { + SQLite -> json.string("sqlite") + MySQL -> json.string("mysql") + PostgreSQL -> json.string("postgresql") + } +} + +pub type GenJson { + GenJson(out: Option(String), indent: Option(String), filename: Option(String)) +} + +fn gen_json_to_json(gen_json: GenJson) -> json.Json { + let GenJson(out:, indent:, filename:) = gen_json + let json_object = case out { + option.None -> [] + option.Some(out) -> [#("out", json.string(out))] + } + let json_object = case indent { + option.None -> json_object + option.Some(indent) -> [#("indent", json.string(indent)), ..json_object] + } + let json_object = case filename { + option.None -> json_object + option.Some(filename) -> [ + #("filename", json.string(filename)), + ..json_object + ] + } + json.object(json_object) +} + +pub type Gen { + Gen(json: Option(GenJson)) +} + +fn gen_to_json(gen: Gen) -> json.Json { + let Gen(json:) = gen + let json_object = case json { + option.None -> [] + option.Some(json) -> [#("json", gen_json_to_json(json))] + } + json.object(json_object) +} + +pub type Sql { + Sql( + schema: Option(String), + queries: Option(Queries), + engine: Engine, + gen: Option(Gen), + ) +} + +fn sql_to_json(sql: Sql) -> json.Json { + let Sql(schema:, queries:, engine:, gen:) = sql + let json_object = [#("engine", engine_to_json(engine))] + let json_object = case schema { + option.None -> json_object + option.Some(schema) -> [#("schema", json.string(schema)), ..json_object] + } + let json_object = case queries { + option.None -> json_object + option.Some(queries) -> [ + #("queries", queries_to_json(queries)), + ..json_object + ] + } + let json_object = case gen { + option.None -> json_object + option.Some(gen) -> [#("gen", gen_to_json(gen)), ..json_object] + } + json.object(json_object) +} + +pub opaque type Version { + Version(String) +} + +fn version_to_json(version: Version) -> json.Json { + let Version(version) = version + json.object([ + #("version", json.string(version)), + ]) +} + +pub type Config { + Config(version: Version, sql: List(Sql)) +} + +fn config_to_json(config: Config) -> json.Json { + let Config(version:, sql:) = config + json.object([ + #("version", version_to_json(version)), + #("sql", json.array(sql, sql_to_json)), + ]) +} + +pub const version_2: Version = Version("2") + +pub fn config_to_json_string(config: Config) -> String { + config_to_json(config) |> json.to_string +} From 5f0441347d39429346bf4e1a57c532c8fcf81f3f Mon Sep 17 00:00:00 2001 From: szelbi Date: Thu, 2 Oct 2025 21:01:48 +0200 Subject: [PATCH 02/20] assign version string directly --- src/parrot/internal/sqlc_config.gleam | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/parrot/internal/sqlc_config.gleam b/src/parrot/internal/sqlc_config.gleam index 7d97c09..7e1dbe1 100644 --- a/src/parrot/internal/sqlc_config.gleam +++ b/src/parrot/internal/sqlc_config.gleam @@ -108,9 +108,7 @@ pub opaque type Version { fn version_to_json(version: Version) -> json.Json { let Version(version) = version - json.object([ - #("version", json.string(version)), - ]) + json.string(version) } pub type Config { From afd28e9b8c1c0d33d10f86e76b5c5be33f96220f Mon Sep 17 00:00:00 2001 From: szelbi Date: Thu, 2 Oct 2025 21:09:25 +0200 Subject: [PATCH 03/20] fix queries property by removing object --- src/parrot/internal/sqlc_config.gleam | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/parrot/internal/sqlc_config.gleam b/src/parrot/internal/sqlc_config.gleam index 7e1dbe1..7b8dd68 100644 --- a/src/parrot/internal/sqlc_config.gleam +++ b/src/parrot/internal/sqlc_config.gleam @@ -8,16 +8,8 @@ pub type Queries { fn queries_to_json(queries: Queries) -> json.Json { case queries { - Query(query) -> - json.object([ - #("type", json.string("query")), - #("query", json.string(query)), - ]) - Queries(queries) -> - json.object([ - #("type", json.string("queries")), - #("queries", json.array(queries, json.string)), - ]) + Query(query) -> json.string(query) + Queries(queries) -> json.array(queries, json.string) } } From f7f1ffea85594b45070d5674b27adbabbdafa19d Mon Sep 17 00:00:00 2001 From: szelbi Date: Thu, 2 Oct 2025 21:14:50 +0200 Subject: [PATCH 04/20] restore initial gleam_json version --- gleam.toml | 2 +- manifest.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gleam.toml b/gleam.toml index 04fdb5c..224b828 100644 --- a/gleam.toml +++ b/gleam.toml @@ -19,7 +19,7 @@ simplifile = ">= 2.2.0 and < 3.0.0" filepath = ">= 1.1.0 and < 2.0.0" exception = ">= 2.0.0 and < 3.0.0" argv = ">= 1.0.2 and < 2.0.0" -gleam_json = ">= 3.0.2 and < 4.0.0" +gleam_json = ">= 3.0.1 and < 4.0.0" gleam_time = ">= 1.2.0 and < 2.0.0" tom = ">= 2.0.0 and < 3.0.0" envoy = ">= 1.0.2 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml index fadae55..954f17d 100644 --- a/manifest.toml +++ b/manifest.toml @@ -35,7 +35,7 @@ given = { version = ">= 6.0.0 and < 7.0.0" } gleam_crypto = { version = ">= 1.5.1 and < 2.0.0" } gleam_http = { version = ">= 4.0.0 and < 5.0.0" } gleam_httpc = { version = ">= 5.0.0 and < 6.0.0" } -gleam_json = { version = ">= 3.0.2 and < 4.0.0" } +gleam_json = { version = ">= 3.0.1 and < 4.0.0" } gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" } gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } gleam_time = { version = ">= 1.2.0 and < 2.0.0" } From 154bbeae0a1a5eb5d9d601b4cda1185579240f62 Mon Sep 17 00:00:00 2001 From: szelbi Date: Thu, 2 Oct 2025 21:23:03 +0200 Subject: [PATCH 05/20] move `sqlc_config` contents to `sqlc` module, adjust names --- src/parrot.gleam | 14 +-- src/parrot/internal/cli.gleam | 12 +-- src/parrot/internal/sqlc.gleam | 141 ++++++++++++++++++++++++-- src/parrot/internal/sqlc_config.gleam | 122 ---------------------- 4 files changed, 142 insertions(+), 147 deletions(-) delete mode 100644 src/parrot/internal/sqlc_config.gleam diff --git a/src/parrot.gleam b/src/parrot.gleam index 8aa141f..4d0a10e 100644 --- a/src/parrot.gleam +++ b/src/parrot.gleam @@ -17,7 +17,6 @@ import parrot/internal/project import parrot/internal/shellout import parrot/internal/spinner import parrot/internal/sqlc -import parrot/internal/sqlc_config import simplifile pub fn main() { @@ -35,7 +34,7 @@ pub fn main() { |> result.map(fn(a) { cli.Generate(a.0, a.1) }) } ["--sqlite", file_path] -> { - Ok(cli.Generate(sqlc_config.SQLite, file_path)) + Ok(cli.Generate(sqlc.SQLite, file_path)) } ["help"] -> Ok(cli.Usage) _ -> Ok(cli.Usage) @@ -58,10 +57,7 @@ pub fn main() { } } -fn cmd_gen( - engine: sqlc_config.Engine, - db: String, -) -> Result(Nil, errors.ParrotError) { +fn cmd_gen(engine: sqlc.Engine, db: String) -> Result(Nil, errors.ParrotError) { let db = case db { "sqlite://" <> db -> db "sqlite:" <> db -> db @@ -117,18 +113,18 @@ fn cmd_gen( |> spinner.start() use schema_sql <- result.try(case engine { - sqlc_config.MySQL -> { + sqlc.MySQL -> { use schema <- result.try(db.fetch_schema_mysql(db)) Ok(schema) } - sqlc_config.PostgreSQL -> { + sqlc.PostgreSQL -> { use schema <- result.try(db.fetch_schema_postgresql(db)) let assert Ok(re) = regexp.from_string("(?m)^\\\\restrict.*\n|^\\\\unrestrict.*\n") let schema = regexp.replace(re, schema, "") Ok(schema) } - sqlc_config.SQLite -> { + sqlc.SQLite -> { use schema <- result.try(db.fetch_schema_sqlite(db)) let sql = string.trim(schema) Ok(sql) diff --git a/src/parrot/internal/cli.gleam b/src/parrot/internal/cli.gleam index 2d9ca39..49ee9d0 100644 --- a/src/parrot/internal/cli.gleam +++ b/src/parrot/internal/cli.gleam @@ -1,7 +1,7 @@ import envoy import given import parrot/internal/errors -import parrot/internal/sqlc_config +import parrot/internal/sqlc pub const usage = " 🦜 Parrot - type-safe SQL in gleam for sqlite, postgresql & mysql @@ -55,19 +55,19 @@ pub const usage = " pub type Command { Usage - Generate(engine: sqlc_config.Engine, db: String) + Generate(engine: sqlc.Engine, db: String) } pub fn engine_from_env(str: String) { case str { - "postgres" <> _ -> Ok(sqlc_config.PostgreSQL) - "mysql" <> _ -> Ok(sqlc_config.MySQL) - "file" | "sqlite" <> _ -> Ok(sqlc_config.SQLite) + "postgres" <> _ -> Ok(sqlc.PostgreSQL) + "mysql" <> _ -> Ok(sqlc.MySQL) + "file" | "sqlite" <> _ -> Ok(sqlc.SQLite) _ -> Error(errors.UnknownEngine(str)) } } -pub fn parse_env(env: String) -> Result(#(sqlc_config.Engine, String), String) { +pub fn parse_env(env: String) -> Result(#(sqlc.Engine, String), String) { let env_result = envoy.get(env) use env_var <- given.error(in: env_result, return: fn(_) { Error("Environment Variable \"DATABASE_URL\" is empty!") diff --git a/src/parrot/internal/sqlc.gleam b/src/parrot/internal/sqlc.gleam index 642e597..19e9855 100644 --- a/src/parrot/internal/sqlc.gleam +++ b/src/parrot/internal/sqlc.gleam @@ -7,6 +7,7 @@ import gleam/bit_array import gleam/crypto import gleam/dynamic import gleam/dynamic/decode +import gleam/json import gleam/option.{type Option, Some} import gleam/result import gleam/set @@ -17,6 +18,129 @@ import parrot/internal/shellout import parrot/internal/sqlc_config import simplifile.{Execute, FilePermissions, Read, Write} +// SQLC Configuration types +pub type Queries { + QuerySingle(String) + QueryMultiple(List(String)) +} + +pub type Engine { + SQLite + MySQL + PostgreSQL +} + +pub type GenJson { + GenJson(out: Option(String), indent: Option(String), filename: Option(String)) +} + +pub type Gen { + Gen(json: Option(GenJson)) +} + +pub type Sql { + Sql( + schema: Option(String), + queries: Option(Queries), + engine: Engine, + gen: Option(Gen), + ) +} + +pub opaque type Version { + Version(String) +} + +pub type Config { + Config(version: Version, sql: List(Sql)) +} + +// SQLC Configuration version constants +pub const version_2: Version = Version("2") + +// SQLC Configuration to-JSON functions +fn queries_to_json(queries: Queries) -> json.Json { + case queries { + QuerySingle(query) -> json.string(query) + QueryMultiple(queries) -> json.array(queries, json.string) + } +} + +fn engine_to_json(engine: Engine) -> json.Json { + case engine { + SQLite -> json.string("sqlite") + MySQL -> json.string("mysql") + PostgreSQL -> json.string("postgresql") + } +} + +fn gen_json_to_json(gen_json: GenJson) -> json.Json { + let GenJson(out:, indent:, filename:) = gen_json + let json_object = case out { + option.None -> [] + option.Some(out) -> [#("out", json.string(out))] + } + let json_object = case indent { + option.None -> json_object + option.Some(indent) -> [#("indent", json.string(indent)), ..json_object] + } + let json_object = case filename { + option.None -> json_object + option.Some(filename) -> [ + #("filename", json.string(filename)), + ..json_object + ] + } + json.object(json_object) +} + +fn sql_to_json(sql: Sql) -> json.Json { + let Sql(schema:, queries:, engine:, gen:) = sql + let json_object = [#("engine", engine_to_json(engine))] + let json_object = case schema { + option.None -> json_object + option.Some(schema) -> [#("schema", json.string(schema)), ..json_object] + } + let json_object = case queries { + option.None -> json_object + option.Some(queries) -> [ + #("queries", queries_to_json(queries)), + ..json_object + ] + } + let json_object = case gen { + option.None -> json_object + option.Some(gen) -> [#("gen", gen_to_json(gen)), ..json_object] + } + json.object(json_object) +} + +fn gen_to_json(gen: Gen) -> json.Json { + let Gen(json:) = gen + let json_object = case json { + option.None -> [] + option.Some(json) -> [#("json", gen_json_to_json(json))] + } + json.object(json_object) +} + +fn version_to_json(version: Version) -> json.Json { + let Version(version) = version + json.string(version) +} + +fn config_to_json(config: Config) -> json.Json { + let Config(version:, sql:) = config + json.object([ + #("version", version_to_json(version)), + #("sql", json.array(sql, sql_to_json)), + ]) +} + +pub fn config_to_json_string(config: Config) -> String { + config_to_json(config) |> json.to_string +} + pub type TypeRef { TypeRef(catalog: String, schema: String, name: String) } @@ -266,19 +390,16 @@ pub fn decode_sqlc(data: dynamic.Dynamic) { decode.run(data, decoder) } -pub fn gen_sqlc_json( - engine: sqlc_config.Engine, - queries: List(String), -) -> String { +pub fn gen_sqlc_json(engine: Engine, queries: List(String)) -> String { let config = - sqlc_config.Config(version: sqlc_config.version_2, sql: [ - sqlc_config.Sql( + Config(version: version_2, sql: [ + Sql( schema: Some("schema.sql"), - queries: Some(sqlc_config.Queries(queries)), + queries: Some(QueryMultiple(queries)), engine:, gen: Some( - sqlc_config.Gen( - json: Some(sqlc_config.GenJson( + Gen( + json: Some(GenJson( out: Some("."), indent: Some(" "), filename: Some("queries.json"), @@ -287,7 +408,7 @@ pub fn gen_sqlc_json( ), ), ]) - sqlc_config.config_to_json_string(config) + config_to_json_string(config) } pub fn sqlc_binary_path() { diff --git a/src/parrot/internal/sqlc_config.gleam b/src/parrot/internal/sqlc_config.gleam deleted file mode 100644 index 7b8dd68..0000000 --- a/src/parrot/internal/sqlc_config.gleam +++ /dev/null @@ -1,122 +0,0 @@ -import gleam/json -import gleam/option.{type Option} - -pub type Queries { - Query(String) - Queries(List(String)) -} - -fn queries_to_json(queries: Queries) -> json.Json { - case queries { - Query(query) -> json.string(query) - Queries(queries) -> json.array(queries, json.string) - } -} - -pub type Engine { - SQLite - MySQL - PostgreSQL -} - -fn engine_to_json(engine: Engine) -> json.Json { - case engine { - SQLite -> json.string("sqlite") - MySQL -> json.string("mysql") - PostgreSQL -> json.string("postgresql") - } -} - -pub type GenJson { - GenJson(out: Option(String), indent: Option(String), filename: Option(String)) -} - -fn gen_json_to_json(gen_json: GenJson) -> json.Json { - let GenJson(out:, indent:, filename:) = gen_json - let json_object = case out { - option.None -> [] - option.Some(out) -> [#("out", json.string(out))] - } - let json_object = case indent { - option.None -> json_object - option.Some(indent) -> [#("indent", json.string(indent)), ..json_object] - } - let json_object = case filename { - option.None -> json_object - option.Some(filename) -> [ - #("filename", json.string(filename)), - ..json_object - ] - } - json.object(json_object) -} - -pub type Gen { - Gen(json: Option(GenJson)) -} - -fn gen_to_json(gen: Gen) -> json.Json { - let Gen(json:) = gen - let json_object = case json { - option.None -> [] - option.Some(json) -> [#("json", gen_json_to_json(json))] - } - json.object(json_object) -} - -pub type Sql { - Sql( - schema: Option(String), - queries: Option(Queries), - engine: Engine, - gen: Option(Gen), - ) -} - -fn sql_to_json(sql: Sql) -> json.Json { - let Sql(schema:, queries:, engine:, gen:) = sql - let json_object = [#("engine", engine_to_json(engine))] - let json_object = case schema { - option.None -> json_object - option.Some(schema) -> [#("schema", json.string(schema)), ..json_object] - } - let json_object = case queries { - option.None -> json_object - option.Some(queries) -> [ - #("queries", queries_to_json(queries)), - ..json_object - ] - } - let json_object = case gen { - option.None -> json_object - option.Some(gen) -> [#("gen", gen_to_json(gen)), ..json_object] - } - json.object(json_object) -} - -pub opaque type Version { - Version(String) -} - -fn version_to_json(version: Version) -> json.Json { - let Version(version) = version - json.string(version) -} - -pub type Config { - Config(version: Version, sql: List(Sql)) -} - -fn config_to_json(config: Config) -> json.Json { - let Config(version:, sql:) = config - json.object([ - #("version", version_to_json(version)), - #("sql", json.array(sql, sql_to_json)), - ]) -} - -pub const version_2: Version = Version("2") - -pub fn config_to_json_string(config: Config) -> String { - config_to_json(config) |> json.to_string -} From 1b589f1f5de26a3de2e8c847dd8fc8eaf2cb1a76 Mon Sep 17 00:00:00 2001 From: szelbi Date: Thu, 2 Oct 2025 21:24:25 +0200 Subject: [PATCH 06/20] rm unused import --- src/parrot/internal/sqlc.gleam | 1 - 1 file changed, 1 deletion(-) diff --git a/src/parrot/internal/sqlc.gleam b/src/parrot/internal/sqlc.gleam index 19e9855..28e17c0 100644 --- a/src/parrot/internal/sqlc.gleam +++ b/src/parrot/internal/sqlc.gleam @@ -15,7 +15,6 @@ import gleam/string import parrot/internal/errors import parrot/internal/project import parrot/internal/shellout -import parrot/internal/sqlc_config import simplifile.{Execute, FilePermissions, Read, Write} // SQLC Configuration types From 09aa13a56286b141c569da977ff59f97aa744bf6 Mon Sep 17 00:00:00 2001 From: szelbi Date: Thu, 2 Oct 2025 21:25:48 +0200 Subject: [PATCH 07/20] rename config v2 const --- src/parrot/internal/sqlc.gleam | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parrot/internal/sqlc.gleam b/src/parrot/internal/sqlc.gleam index 28e17c0..0635a7d 100644 --- a/src/parrot/internal/sqlc.gleam +++ b/src/parrot/internal/sqlc.gleam @@ -55,7 +55,7 @@ pub type Config { } // SQLC Configuration version constants -pub const version_2: Version = Version("2") +pub const config_version_2: Version = Version("2") // SQLC Configuration to-JSON functions fn queries_to_json(queries: Queries) -> json.Json { @@ -391,7 +391,7 @@ pub fn decode_sqlc(data: dynamic.Dynamic) { pub fn gen_sqlc_json(engine: Engine, queries: List(String)) -> String { let config = - Config(version: version_2, sql: [ + Config(version: config_version_2, sql: [ Sql( schema: Some("schema.sql"), queries: Some(QueryMultiple(queries)), From 723e04f1dfa6fd558123202cb141237ba0982532 Mon Sep 17 00:00:00 2001 From: szelbi Date: Thu, 2 Oct 2025 22:37:22 +0200 Subject: [PATCH 08/20] make types and functions private, remove unnecessary const --- src/parrot/internal/sqlc.gleam | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/parrot/internal/sqlc.gleam b/src/parrot/internal/sqlc.gleam index 0635a7d..84e99d3 100644 --- a/src/parrot/internal/sqlc.gleam +++ b/src/parrot/internal/sqlc.gleam @@ -18,7 +18,7 @@ import parrot/internal/shellout import simplifile.{Execute, FilePermissions, Read, Write} // SQLC Configuration types -pub type Queries { +type Queries { QuerySingle(String) QueryMultiple(List(String)) } @@ -29,15 +29,15 @@ pub type Engine { PostgreSQL } -pub type GenJson { +type GenJson { GenJson(out: Option(String), indent: Option(String), filename: Option(String)) } -pub type Gen { +type Gen { Gen(json: Option(GenJson)) } -pub type Sql { +type Sql { Sql( schema: Option(String), queries: Option(Queries), @@ -46,17 +46,14 @@ pub type Sql { ) } -pub opaque type Version { +type Version { Version(String) } -pub type Config { +type Config { Config(version: Version, sql: List(Sql)) } -// SQLC Configuration version constants -pub const config_version_2: Version = Version("2") - // SQLC Configuration to-JSON functions fn queries_to_json(queries: Queries) -> json.Json { case queries { @@ -136,7 +133,7 @@ fn config_to_json(config: Config) -> json.Json { ]) } -pub fn config_to_json_string(config: Config) -> String { +fn config_to_json_string(config: Config) -> String { config_to_json(config) |> json.to_string } @@ -391,7 +388,7 @@ pub fn decode_sqlc(data: dynamic.Dynamic) { pub fn gen_sqlc_json(engine: Engine, queries: List(String)) -> String { let config = - Config(version: config_version_2, sql: [ + Config(version: Version("2"), sql: [ Sql( schema: Some("schema.sql"), queries: Some(QueryMultiple(queries)), From e5e549305b18c0b56a343b5924d814f01537479a Mon Sep 17 00:00:00 2001 From: szelbi Date: Thu, 2 Oct 2025 22:41:01 +0200 Subject: [PATCH 09/20] list specific versions as variants --- src/parrot/internal/sqlc.gleam | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/parrot/internal/sqlc.gleam b/src/parrot/internal/sqlc.gleam index 84e99d3..6aab0dd 100644 --- a/src/parrot/internal/sqlc.gleam +++ b/src/parrot/internal/sqlc.gleam @@ -47,7 +47,7 @@ type Sql { } type Version { - Version(String) + Version2 } type Config { @@ -121,7 +121,9 @@ fn gen_to_json(gen: Gen) -> json.Json { } fn version_to_json(version: Version) -> json.Json { - let Version(version) = version + let version = case version { + Version2 -> "2" + } json.string(version) } From 718729bae58aca22e02366a5dbffd708874f5469 Mon Sep 17 00:00:00 2001 From: szelbi Date: Thu, 2 Oct 2025 22:41:28 +0200 Subject: [PATCH 10/20] fix version constructor --- src/parrot/internal/sqlc.gleam | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parrot/internal/sqlc.gleam b/src/parrot/internal/sqlc.gleam index 6aab0dd..c3e15e4 100644 --- a/src/parrot/internal/sqlc.gleam +++ b/src/parrot/internal/sqlc.gleam @@ -390,7 +390,7 @@ pub fn decode_sqlc(data: dynamic.Dynamic) { pub fn gen_sqlc_json(engine: Engine, queries: List(String)) -> String { let config = - Config(version: Version("2"), sql: [ + Config(version: Version2, sql: [ Sql( schema: Some("schema.sql"), queries: Some(QueryMultiple(queries)), From a00e098b1e9c6f3e7fdf706b85f83d5a75946694 Mon Sep 17 00:00:00 2001 From: szelbi Date: Thu, 2 Oct 2025 22:46:03 +0200 Subject: [PATCH 11/20] use json.string once for Engine --- src/parrot/internal/sqlc.gleam | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/parrot/internal/sqlc.gleam b/src/parrot/internal/sqlc.gleam index c3e15e4..d57b59f 100644 --- a/src/parrot/internal/sqlc.gleam +++ b/src/parrot/internal/sqlc.gleam @@ -63,11 +63,12 @@ fn queries_to_json(queries: Queries) -> json.Json { } fn engine_to_json(engine: Engine) -> json.Json { - case engine { - SQLite -> json.string("sqlite") - MySQL -> json.string("mysql") - PostgreSQL -> json.string("postgresql") + let engine = case engine { + SQLite -> "sqlite" + MySQL -> "mysql" + PostgreSQL -> "postgresql" } + json.string(engine) } fn gen_json_to_json(gen_json: GenJson) -> json.Json { From fa8b473ae2fb21017f0448d978762ba885b40bd3 Mon Sep 17 00:00:00 2001 From: szelbi Date: Thu, 2 Oct 2025 22:55:05 +0200 Subject: [PATCH 12/20] rename Queries constructors for consistency --- src/parrot/internal/sqlc.gleam | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/parrot/internal/sqlc.gleam b/src/parrot/internal/sqlc.gleam index d57b59f..3f71852 100644 --- a/src/parrot/internal/sqlc.gleam +++ b/src/parrot/internal/sqlc.gleam @@ -19,8 +19,8 @@ import simplifile.{Execute, FilePermissions, Read, Write} // SQLC Configuration types type Queries { - QuerySingle(String) - QueryMultiple(List(String)) + QueriesSingle(String) + QueriesMultiple(List(String)) } pub type Engine { @@ -57,8 +57,8 @@ type Config { // SQLC Configuration to-JSON functions fn queries_to_json(queries: Queries) -> json.Json { case queries { - QuerySingle(query) -> json.string(query) - QueryMultiple(queries) -> json.array(queries, json.string) + QueriesSingle(query) -> json.string(query) + QueriesMultiple(queries) -> json.array(queries, json.string) } } @@ -394,7 +394,7 @@ pub fn gen_sqlc_json(engine: Engine, queries: List(String)) -> String { Config(version: Version2, sql: [ Sql( schema: Some("schema.sql"), - queries: Some(QueryMultiple(queries)), + queries: Some(QueriesMultiple(queries)), engine:, gen: Some( Gen( From f4b9d19414757d994474bbe0cdc5984b3355e9e5 Mon Sep 17 00:00:00 2001 From: szelbi Date: Fri, 3 Oct 2025 10:20:57 +0200 Subject: [PATCH 13/20] change types in a way that you won't be able to mix different config version types --- src/parrot.gleam | 2 +- src/parrot/internal/cli.gleam | 4 +- src/parrot/internal/sqlc.gleam | 100 ++++++++++++++++++--------------- 3 files changed, 58 insertions(+), 48 deletions(-) diff --git a/src/parrot.gleam b/src/parrot.gleam index 4d0a10e..09d2697 100644 --- a/src/parrot.gleam +++ b/src/parrot.gleam @@ -57,7 +57,7 @@ pub fn main() { } } -fn cmd_gen(engine: sqlc.Engine, db: String) -> Result(Nil, errors.ParrotError) { +fn cmd_gen(engine: sqlc.V2Engine, db: String) -> Result(Nil, errors.ParrotError) { let db = case db { "sqlite://" <> db -> db "sqlite:" <> db -> db diff --git a/src/parrot/internal/cli.gleam b/src/parrot/internal/cli.gleam index 49ee9d0..00a8eb9 100644 --- a/src/parrot/internal/cli.gleam +++ b/src/parrot/internal/cli.gleam @@ -55,7 +55,7 @@ pub const usage = " pub type Command { Usage - Generate(engine: sqlc.Engine, db: String) + Generate(engine: sqlc.V2Engine, db: String) } pub fn engine_from_env(str: String) { @@ -67,7 +67,7 @@ pub fn engine_from_env(str: String) { } } -pub fn parse_env(env: String) -> Result(#(sqlc.Engine, String), String) { +pub fn parse_env(env: String) -> Result(#(sqlc.V2Engine, String), String) { let env_result = envoy.get(env) use env_var <- given.error(in: env_result, return: fn(_) { Error("Environment Variable \"DATABASE_URL\" is empty!") diff --git a/src/parrot/internal/sqlc.gleam b/src/parrot/internal/sqlc.gleam index 3f71852..5326f46 100644 --- a/src/parrot/internal/sqlc.gleam +++ b/src/parrot/internal/sqlc.gleam @@ -17,52 +17,60 @@ import parrot/internal/project import parrot/internal/shellout import simplifile.{Execute, FilePermissions, Read, Write} -// SQLC Configuration types -type Queries { - QueriesSingle(String) - QueriesMultiple(List(String)) -} - -pub type Engine { +// SQLC Configuration v2 types +pub type V2Engine { SQLite MySQL PostgreSQL } -type GenJson { - GenJson(out: Option(String), indent: Option(String), filename: Option(String)) +type V2Queries { + V2Query(String) + V2Queries(List(String)) +} + +type V2GenJson { + V2GenJson( + out: Option(String), + indent: Option(String), + filename: Option(String), + ) } -type Gen { - Gen(json: Option(GenJson)) +type V2Gen { + V2Gen(json: Option(V2GenJson)) } -type Sql { - Sql( +type V2Sql { + V2Sql( schema: Option(String), - queries: Option(Queries), - engine: Engine, - gen: Option(Gen), + queries: Option(V2Queries), + engine: V2Engine, + gen: Option(V2Gen), ) } -type Version { +type Version2 { Version2 } +/// If needed, we can create a separate type for Version 1 in the future and +/// use it in a separate Config variant. +/// This will prevent us from mixing structures of different versions in the +/// same config. type Config { - Config(version: Version, sql: List(Sql)) + ConfigV2(version: Version2, sql: List(V2Sql)) } -// SQLC Configuration to-JSON functions -fn queries_to_json(queries: Queries) -> json.Json { +// SQLC Configuration v2 to-JSON functions +fn v2_queries_to_json(queries: V2Queries) -> json.Json { case queries { - QueriesSingle(query) -> json.string(query) - QueriesMultiple(queries) -> json.array(queries, json.string) + V2Query(query) -> json.string(query) + V2Queries(queries) -> json.array(queries, json.string) } } -fn engine_to_json(engine: Engine) -> json.Json { +fn v2_engine_to_json(engine: V2Engine) -> json.Json { let engine = case engine { SQLite -> "sqlite" MySQL -> "mysql" @@ -71,8 +79,8 @@ fn engine_to_json(engine: Engine) -> json.Json { json.string(engine) } -fn gen_json_to_json(gen_json: GenJson) -> json.Json { - let GenJson(out:, indent:, filename:) = gen_json +fn v2_gen_json_to_json(gen_json: V2GenJson) -> json.Json { + let V2GenJson(out:, indent:, filename:) = gen_json let json_object = case out { option.None -> [] option.Some(out) -> [#("out", json.string(out))] @@ -91,9 +99,9 @@ fn gen_json_to_json(gen_json: GenJson) -> json.Json { json.object(json_object) } -fn sql_to_json(sql: Sql) -> json.Json { - let Sql(schema:, queries:, engine:, gen:) = sql - let json_object = [#("engine", engine_to_json(engine))] +fn v2_sql_to_json(sql: V2Sql) -> json.Json { + let V2Sql(schema:, queries:, engine:, gen:) = sql + let json_object = [#("engine", v2_engine_to_json(engine))] let json_object = case schema { option.None -> json_object option.Some(schema) -> [#("schema", json.string(schema)), ..json_object] @@ -101,27 +109,27 @@ fn sql_to_json(sql: Sql) -> json.Json { let json_object = case queries { option.None -> json_object option.Some(queries) -> [ - #("queries", queries_to_json(queries)), + #("queries", v2_queries_to_json(queries)), ..json_object ] } let json_object = case gen { option.None -> json_object - option.Some(gen) -> [#("gen", gen_to_json(gen)), ..json_object] + option.Some(gen) -> [#("gen", v2_gen_to_json(gen)), ..json_object] } json.object(json_object) } -fn gen_to_json(gen: Gen) -> json.Json { - let Gen(json:) = gen +fn v2_gen_to_json(gen: V2Gen) -> json.Json { + let V2Gen(json:) = gen let json_object = case json { option.None -> [] - option.Some(json) -> [#("json", gen_json_to_json(json))] + option.Some(json) -> [#("json", v2_gen_json_to_json(json))] } json.object(json_object) } -fn version_to_json(version: Version) -> json.Json { +fn version2_to_json(version: Version2) -> json.Json { let version = case version { Version2 -> "2" } @@ -129,11 +137,13 @@ fn version_to_json(version: Version) -> json.Json { } fn config_to_json(config: Config) -> json.Json { - let Config(version:, sql:) = config - json.object([ - #("version", version_to_json(version)), - #("sql", json.array(sql, sql_to_json)), - ]) + case config { + ConfigV2(version:, sql:) -> + json.object([ + #("version", version2_to_json(version)), + #("sql", json.array(sql, v2_sql_to_json)), + ]) + } } fn config_to_json_string(config: Config) -> String { @@ -389,16 +399,16 @@ pub fn decode_sqlc(data: dynamic.Dynamic) { decode.run(data, decoder) } -pub fn gen_sqlc_json(engine: Engine, queries: List(String)) -> String { +pub fn gen_sqlc_json(engine: V2Engine, queries: List(String)) -> String { let config = - Config(version: Version2, sql: [ - Sql( + ConfigV2(version: Version2, sql: [ + V2Sql( schema: Some("schema.sql"), - queries: Some(QueriesMultiple(queries)), + queries: Some(V2Queries(queries)), engine:, gen: Some( - Gen( - json: Some(GenJson( + V2Gen( + json: Some(V2GenJson( out: Some("."), indent: Some(" "), filename: Some("queries.json"), From 6837aeda791af79da4e73c7e2db223a77b353bac Mon Sep 17 00:00:00 2001 From: szelbi Date: Fri, 3 Oct 2025 10:26:46 +0200 Subject: [PATCH 14/20] update docs, simplify Version2 encoder --- src/parrot/internal/sqlc.gleam | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/parrot/internal/sqlc.gleam b/src/parrot/internal/sqlc.gleam index 5326f46..f4f2706 100644 --- a/src/parrot/internal/sqlc.gleam +++ b/src/parrot/internal/sqlc.gleam @@ -1,5 +1,5 @@ -//// This module decodes the JSON generated by sqlc -//// +//// This module generates the JSON config, which is used when running sqlc, and +//// decodes the JSON that sqlc generates. import filepath import given @@ -54,8 +54,8 @@ type Version2 { Version2 } -/// If needed, we can create a separate type for Version 1 in the future and -/// use it in a separate Config variant. +/// If necessary, we can create a separate type for Version 1 and use it in +/// a new Config variant. /// This will prevent us from mixing structures of different versions in the /// same config. type Config { @@ -129,11 +129,8 @@ fn v2_gen_to_json(gen: V2Gen) -> json.Json { json.object(json_object) } -fn version2_to_json(version: Version2) -> json.Json { - let version = case version { - Version2 -> "2" - } - json.string(version) +fn version2_to_json(_: Version2) -> json.Json { + json.string("2") } fn config_to_json(config: Config) -> json.Json { From 45e5ae930a0e35f08948964b588341d97f2bcde0 Mon Sep 17 00:00:00 2001 From: szelbi Date: Fri, 3 Oct 2025 10:54:42 +0200 Subject: [PATCH 15/20] allow to generate different configs, based on the CLI params --- src/parrot.gleam | 16 +++++----- src/parrot/internal/cli.gleam | 8 ++--- src/parrot/internal/sqlc.gleam | 57 ++++++++++++++++++++-------------- 3 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/parrot.gleam b/src/parrot.gleam index 09d2697..47e8a21 100644 --- a/src/parrot.gleam +++ b/src/parrot.gleam @@ -23,18 +23,18 @@ pub fn main() { let cmd: Result(cli.Command, String) = case argv.load().arguments { [] -> { cli.parse_env("DATABASE_URL") - |> result.map(fn(a) { cli.Generate(a.0, a.1) }) + |> result.map(fn(a) { cli.Generate(sqlc.V2Engine(a.0), a.1) }) } ["--env-var", env] -> { cli.parse_env(env) - |> result.map(fn(a) { cli.Generate(a.0, a.1) }) + |> result.map(fn(a) { cli.Generate(sqlc.V2Engine(a.0), a.1) }) } ["-e", env] -> { cli.parse_env(env) - |> result.map(fn(a) { cli.Generate(a.0, a.1) }) + |> result.map(fn(a) { cli.Generate(sqlc.V2Engine(a.0), a.1) }) } ["--sqlite", file_path] -> { - Ok(cli.Generate(sqlc.SQLite, file_path)) + Ok(cli.Generate(sqlc.V2Engine(sqlc.V2SQLite), file_path)) } ["help"] -> Ok(cli.Usage) _ -> Ok(cli.Usage) @@ -57,7 +57,7 @@ pub fn main() { } } -fn cmd_gen(engine: sqlc.V2Engine, db: String) -> Result(Nil, errors.ParrotError) { +fn cmd_gen(engine: sqlc.Engine, db: String) -> Result(Nil, errors.ParrotError) { let db = case db { "sqlite://" <> db -> db "sqlite:" <> db -> db @@ -113,18 +113,18 @@ fn cmd_gen(engine: sqlc.V2Engine, db: String) -> Result(Nil, errors.ParrotError) |> spinner.start() use schema_sql <- result.try(case engine { - sqlc.MySQL -> { + sqlc.V2Engine(sqlc.V2MySQL) -> { use schema <- result.try(db.fetch_schema_mysql(db)) Ok(schema) } - sqlc.PostgreSQL -> { + sqlc.V2Engine(sqlc.V2PostgreSQL) -> { use schema <- result.try(db.fetch_schema_postgresql(db)) let assert Ok(re) = regexp.from_string("(?m)^\\\\restrict.*\n|^\\\\unrestrict.*\n") let schema = regexp.replace(re, schema, "") Ok(schema) } - sqlc.SQLite -> { + sqlc.V2Engine(sqlc.V2SQLite) -> { use schema <- result.try(db.fetch_schema_sqlite(db)) let sql = string.trim(schema) Ok(sql) diff --git a/src/parrot/internal/cli.gleam b/src/parrot/internal/cli.gleam index 00a8eb9..dab9078 100644 --- a/src/parrot/internal/cli.gleam +++ b/src/parrot/internal/cli.gleam @@ -55,14 +55,14 @@ pub const usage = " pub type Command { Usage - Generate(engine: sqlc.V2Engine, db: String) + Generate(engine: sqlc.Engine, db: String) } pub fn engine_from_env(str: String) { case str { - "postgres" <> _ -> Ok(sqlc.PostgreSQL) - "mysql" <> _ -> Ok(sqlc.MySQL) - "file" | "sqlite" <> _ -> Ok(sqlc.SQLite) + "postgres" <> _ -> Ok(sqlc.V2PostgreSQL) + "mysql" <> _ -> Ok(sqlc.V2MySQL) + "file" | "sqlite" <> _ -> Ok(sqlc.V2SQLite) _ -> Error(errors.UnknownEngine(str)) } } diff --git a/src/parrot/internal/sqlc.gleam b/src/parrot/internal/sqlc.gleam index f4f2706..bb176c8 100644 --- a/src/parrot/internal/sqlc.gleam +++ b/src/parrot/internal/sqlc.gleam @@ -17,11 +17,18 @@ import parrot/internal/project import parrot/internal/shellout import simplifile.{Execute, FilePermissions, Read, Write} -// SQLC Configuration v2 types +/// Different versions of sqlc support different sets of engines. +/// Thanks to this type, we can generate different versions of the config based +/// on user input if we decide to create a parameter for this. +/// For now, V2 is the only choice, of course. +pub type Engine { + V2Engine(V2Engine) +} + pub type V2Engine { - SQLite - MySQL - PostgreSQL + V2SQLite + V2MySQL + V2PostgreSQL } type V2Queries { @@ -62,7 +69,6 @@ type Config { ConfigV2(version: Version2, sql: List(V2Sql)) } -// SQLC Configuration v2 to-JSON functions fn v2_queries_to_json(queries: V2Queries) -> json.Json { case queries { V2Query(query) -> json.string(query) @@ -72,9 +78,9 @@ fn v2_queries_to_json(queries: V2Queries) -> json.Json { fn v2_engine_to_json(engine: V2Engine) -> json.Json { let engine = case engine { - SQLite -> "sqlite" - MySQL -> "mysql" - PostgreSQL -> "postgresql" + V2SQLite -> "sqlite" + V2MySQL -> "mysql" + V2PostgreSQL -> "postgresql" } json.string(engine) } @@ -396,24 +402,27 @@ pub fn decode_sqlc(data: dynamic.Dynamic) { decode.run(data, decoder) } -pub fn gen_sqlc_json(engine: V2Engine, queries: List(String)) -> String { - let config = - ConfigV2(version: Version2, sql: [ - V2Sql( - schema: Some("schema.sql"), - queries: Some(V2Queries(queries)), - engine:, - gen: Some( - V2Gen( - json: Some(V2GenJson( - out: Some("."), - indent: Some(" "), - filename: Some("queries.json"), - )), +pub fn gen_sqlc_json(engine: Engine, queries: List(String)) -> String { + let config = case engine { + V2Engine(engine) -> + ConfigV2(version: Version2, sql: [ + V2Sql( + schema: Some("schema.sql"), + queries: Some(V2Queries(queries)), + engine:, + gen: Some( + V2Gen( + json: Some(V2GenJson( + out: Some("."), + indent: Some(" "), + filename: Some("queries.json"), + )), + ), ), ), - ), - ]) + ]) + } + config_to_json_string(config) } From a116fa420b6d798d2ed4f73879c77937921c43f5 Mon Sep 17 00:00:00 2001 From: szelbi Date: Thu, 9 Oct 2025 17:53:55 +0200 Subject: [PATCH 16/20] single config version --- src/parrot.gleam | 14 ++-- src/parrot/internal/cli.gleam | 8 +-- src/parrot/internal/sqlc.gleam | 119 ++++++++++++++------------------- 3 files changed, 61 insertions(+), 80 deletions(-) diff --git a/src/parrot.gleam b/src/parrot.gleam index 47e8a21..4d0a10e 100644 --- a/src/parrot.gleam +++ b/src/parrot.gleam @@ -23,18 +23,18 @@ pub fn main() { let cmd: Result(cli.Command, String) = case argv.load().arguments { [] -> { cli.parse_env("DATABASE_URL") - |> result.map(fn(a) { cli.Generate(sqlc.V2Engine(a.0), a.1) }) + |> result.map(fn(a) { cli.Generate(a.0, a.1) }) } ["--env-var", env] -> { cli.parse_env(env) - |> result.map(fn(a) { cli.Generate(sqlc.V2Engine(a.0), a.1) }) + |> result.map(fn(a) { cli.Generate(a.0, a.1) }) } ["-e", env] -> { cli.parse_env(env) - |> result.map(fn(a) { cli.Generate(sqlc.V2Engine(a.0), a.1) }) + |> result.map(fn(a) { cli.Generate(a.0, a.1) }) } ["--sqlite", file_path] -> { - Ok(cli.Generate(sqlc.V2Engine(sqlc.V2SQLite), file_path)) + Ok(cli.Generate(sqlc.SQLite, file_path)) } ["help"] -> Ok(cli.Usage) _ -> Ok(cli.Usage) @@ -113,18 +113,18 @@ fn cmd_gen(engine: sqlc.Engine, db: String) -> Result(Nil, errors.ParrotError) { |> spinner.start() use schema_sql <- result.try(case engine { - sqlc.V2Engine(sqlc.V2MySQL) -> { + sqlc.MySQL -> { use schema <- result.try(db.fetch_schema_mysql(db)) Ok(schema) } - sqlc.V2Engine(sqlc.V2PostgreSQL) -> { + sqlc.PostgreSQL -> { use schema <- result.try(db.fetch_schema_postgresql(db)) let assert Ok(re) = regexp.from_string("(?m)^\\\\restrict.*\n|^\\\\unrestrict.*\n") let schema = regexp.replace(re, schema, "") Ok(schema) } - sqlc.V2Engine(sqlc.V2SQLite) -> { + sqlc.SQLite -> { use schema <- result.try(db.fetch_schema_sqlite(db)) let sql = string.trim(schema) Ok(sql) diff --git a/src/parrot/internal/cli.gleam b/src/parrot/internal/cli.gleam index dab9078..49ee9d0 100644 --- a/src/parrot/internal/cli.gleam +++ b/src/parrot/internal/cli.gleam @@ -60,14 +60,14 @@ pub type Command { pub fn engine_from_env(str: String) { case str { - "postgres" <> _ -> Ok(sqlc.V2PostgreSQL) - "mysql" <> _ -> Ok(sqlc.V2MySQL) - "file" | "sqlite" <> _ -> Ok(sqlc.V2SQLite) + "postgres" <> _ -> Ok(sqlc.PostgreSQL) + "mysql" <> _ -> Ok(sqlc.MySQL) + "file" | "sqlite" <> _ -> Ok(sqlc.SQLite) _ -> Error(errors.UnknownEngine(str)) } } -pub fn parse_env(env: String) -> Result(#(sqlc.V2Engine, String), String) { +pub fn parse_env(env: String) -> Result(#(sqlc.Engine, String), String) { let env_result = envoy.get(env) use env_var <- given.error(in: env_result, return: fn(_) { Error("Environment Variable \"DATABASE_URL\" is empty!") diff --git a/src/parrot/internal/sqlc.gleam b/src/parrot/internal/sqlc.gleam index bb176c8..b3687db 100644 --- a/src/parrot/internal/sqlc.gleam +++ b/src/parrot/internal/sqlc.gleam @@ -17,43 +17,31 @@ import parrot/internal/project import parrot/internal/shellout import simplifile.{Execute, FilePermissions, Read, Write} -/// Different versions of sqlc support different sets of engines. -/// Thanks to this type, we can generate different versions of the config based -/// on user input if we decide to create a parameter for this. -/// For now, V2 is the only choice, of course. pub type Engine { - V2Engine(V2Engine) + SQLite + MySQL + PostgreSQL } -pub type V2Engine { - V2SQLite - V2MySQL - V2PostgreSQL +type Queries { + QueriesSingle(String) + QueriesMultiple(List(String)) } -type V2Queries { - V2Query(String) - V2Queries(List(String)) +type GenJson { + GenJson(out: Option(String), indent: Option(String), filename: Option(String)) } -type V2GenJson { - V2GenJson( - out: Option(String), - indent: Option(String), - filename: Option(String), - ) -} - -type V2Gen { - V2Gen(json: Option(V2GenJson)) +type Gen { + Gen(json: Option(GenJson)) } -type V2Sql { - V2Sql( +type Sql { + Sql( schema: Option(String), - queries: Option(V2Queries), - engine: V2Engine, - gen: Option(V2Gen), + queries: Option(Queries), + engine: Engine, + gen: Option(Gen), ) } @@ -61,32 +49,28 @@ type Version2 { Version2 } -/// If necessary, we can create a separate type for Version 1 and use it in -/// a new Config variant. -/// This will prevent us from mixing structures of different versions in the -/// same config. type Config { - ConfigV2(version: Version2, sql: List(V2Sql)) + Config(version: Version2, sql: List(Sql)) } -fn v2_queries_to_json(queries: V2Queries) -> json.Json { +fn queries_to_json(queries: Queries) -> json.Json { case queries { - V2Query(query) -> json.string(query) - V2Queries(queries) -> json.array(queries, json.string) + QueriesSingle(query) -> json.string(query) + QueriesMultiple(queries) -> json.array(queries, json.string) } } -fn v2_engine_to_json(engine: V2Engine) -> json.Json { +fn engine_to_json(engine: Engine) -> json.Json { let engine = case engine { - V2SQLite -> "sqlite" - V2MySQL -> "mysql" - V2PostgreSQL -> "postgresql" + SQLite -> "sqlite" + MySQL -> "mysql" + PostgreSQL -> "postgresql" } json.string(engine) } -fn v2_gen_json_to_json(gen_json: V2GenJson) -> json.Json { - let V2GenJson(out:, indent:, filename:) = gen_json +fn gen_json_to_json(gen_json: GenJson) -> json.Json { + let GenJson(out:, indent:, filename:) = gen_json let json_object = case out { option.None -> [] option.Some(out) -> [#("out", json.string(out))] @@ -105,9 +89,9 @@ fn v2_gen_json_to_json(gen_json: V2GenJson) -> json.Json { json.object(json_object) } -fn v2_sql_to_json(sql: V2Sql) -> json.Json { - let V2Sql(schema:, queries:, engine:, gen:) = sql - let json_object = [#("engine", v2_engine_to_json(engine))] +fn sql_to_json(sql: Sql) -> json.Json { + let Sql(schema:, queries:, engine:, gen:) = sql + let json_object = [#("engine", engine_to_json(engine))] let json_object = case schema { option.None -> json_object option.Some(schema) -> [#("schema", json.string(schema)), ..json_object] @@ -115,22 +99,22 @@ fn v2_sql_to_json(sql: V2Sql) -> json.Json { let json_object = case queries { option.None -> json_object option.Some(queries) -> [ - #("queries", v2_queries_to_json(queries)), + #("queries", queries_to_json(queries)), ..json_object ] } let json_object = case gen { option.None -> json_object - option.Some(gen) -> [#("gen", v2_gen_to_json(gen)), ..json_object] + option.Some(gen) -> [#("gen", gen_to_json(gen)), ..json_object] } json.object(json_object) } -fn v2_gen_to_json(gen: V2Gen) -> json.Json { - let V2Gen(json:) = gen +fn gen_to_json(gen: Gen) -> json.Json { + let Gen(json:) = gen let json_object = case json { option.None -> [] - option.Some(json) -> [#("json", v2_gen_json_to_json(json))] + option.Some(json) -> [#("json", gen_json_to_json(json))] } json.object(json_object) } @@ -141,10 +125,10 @@ fn version2_to_json(_: Version2) -> json.Json { fn config_to_json(config: Config) -> json.Json { case config { - ConfigV2(version:, sql:) -> + Config(version:, sql:) -> json.object([ #("version", version2_to_json(version)), - #("sql", json.array(sql, v2_sql_to_json)), + #("sql", json.array(sql, sql_to_json)), ]) } } @@ -403,26 +387,23 @@ pub fn decode_sqlc(data: dynamic.Dynamic) { } pub fn gen_sqlc_json(engine: Engine, queries: List(String)) -> String { - let config = case engine { - V2Engine(engine) -> - ConfigV2(version: Version2, sql: [ - V2Sql( - schema: Some("schema.sql"), - queries: Some(V2Queries(queries)), - engine:, - gen: Some( - V2Gen( - json: Some(V2GenJson( - out: Some("."), - indent: Some(" "), - filename: Some("queries.json"), - )), - ), + let config = + Config(version: Version2, sql: [ + Sql( + schema: Some("schema.sql"), + queries: Some(QueriesMultiple(queries)), + engine:, + gen: Some( + Gen( + json: Some(GenJson( + out: Some("."), + indent: Some(" "), + filename: Some("queries.json"), + )), ), ), - ]) - } - + ), + ]) config_to_json_string(config) } From 8e5d68745a0caec46df5faf4b526c58b6ddb5a89 Mon Sep 17 00:00:00 2001 From: Daniel Kurz Date: Fri, 10 Oct 2025 13:01:43 +0200 Subject: [PATCH 17/20] delete existing .parrot directory in integration tests --- integration/mysql/Justfile | 1 + integration/psql/Justfile | 1 + integration/sqlite/Justfile | 1 + 3 files changed, 3 insertions(+) diff --git a/integration/mysql/Justfile b/integration/mysql/Justfile index d9a0251..18b86fe 100644 --- a/integration/mysql/Justfile +++ b/integration/mysql/Justfile @@ -6,6 +6,7 @@ run: test: #!/bin/bash + rm -rf build/.parrot rm -f src/app/sql.gleam ../../bin/mysql.sh ./priv/reset.sh diff --git a/integration/psql/Justfile b/integration/psql/Justfile index 4ce0d34..2031960 100644 --- a/integration/psql/Justfile +++ b/integration/psql/Justfile @@ -6,6 +6,7 @@ run: test: #!/bin/bash + rm -rf build/.parrot rm -f src/app/sql.gleam ../../bin/psql.sh ./priv/reset.sh diff --git a/integration/sqlite/Justfile b/integration/sqlite/Justfile index 598750d..d1afecb 100644 --- a/integration/sqlite/Justfile +++ b/integration/sqlite/Justfile @@ -8,6 +8,7 @@ parrot: test: #!/bin/bash + rm -rf build/.parrot rm -f src/app/sql.gleam rm -f {{SQLITE_FILE}} sqlite3 {{SQLITE_FILE}} < ./priv/schema.sql From 63d72a1644149afcc28a6704ff9f4af17f9ebadb Mon Sep 17 00:00:00 2001 From: Daniel Kurz Date: Fri, 10 Oct 2025 13:16:13 +0200 Subject: [PATCH 18/20] fix mysql integration test (schema) --- integration/mysql/priv/schema.sql | 4 ++-- integration/mysql/src/app/sql.gleam | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/integration/mysql/priv/schema.sql b/integration/mysql/priv/schema.sql index 41265c1..21df024 100644 --- a/integration/mysql/priv/schema.sql +++ b/integration/mysql/priv/schema.sql @@ -1,5 +1,5 @@ -drop table if exists users; drop table if exists posts; +drop table if exists users; create table users ( id int auto_increment primary key, @@ -14,7 +14,7 @@ create table posts ( created_at timestamp default current_timestamp, title varchar(255) not null unique, - user_id integer not null, + user_id integer, foreign key (user_id) references users(id) on delete cascade ); diff --git a/integration/mysql/src/app/sql.gleam b/integration/mysql/src/app/sql.gleam index 638da47..0f7981a 100644 --- a/integration/mysql/src/app/sql.gleam +++ b/integration/mysql/src/app/sql.gleam @@ -163,7 +163,7 @@ pub fn get_user_by_username_decoder() -> decode.Decoder(GetUserByUsername) { } pub type PostsByUsername { - PostsByUsername(id: Int, title: String, user_id: Int) + PostsByUsername(id: Int, title: String, user_id: Option(Int)) } pub fn posts_by_username(username username: String) { @@ -184,12 +184,12 @@ where user_id = ( pub fn posts_by_username_decoder() -> decode.Decoder(PostsByUsername) { use id <- decode.field(0, decode.int) use title <- decode.field(1, decode.string) - use user_id <- decode.field(2, decode.int) + use user_id <- decode.field(2, decode.optional(decode.int)) decode.success(PostsByUsername(id:, title:, user_id:)) } pub type PostsByAdmins { - PostsByAdmins(id: Int, title: String, user_id: Int) + PostsByAdmins(id: Int, title: String, user_id: Option(Int)) } pub fn posts_by_admins() { @@ -210,6 +210,6 @@ where user_id in ( pub fn posts_by_admins_decoder() -> decode.Decoder(PostsByAdmins) { use id <- decode.field(0, decode.int) use title <- decode.field(1, decode.string) - use user_id <- decode.field(2, decode.int) + use user_id <- decode.field(2, decode.optional(decode.int)) decode.success(PostsByAdmins(id:, title:, user_id:)) } From 533ffbb4352ece26cbec82972cdc81b2e870b911 Mon Sep 17 00:00:00 2001 From: Daniel Kurz Date: Fri, 10 Oct 2025 13:47:29 +0200 Subject: [PATCH 19/20] explicitly link sqlc config file --- src/parrot.gleam | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/parrot.gleam b/src/parrot.gleam index 4d0a10e..7e9d3c1 100644 --- a/src/parrot.gleam +++ b/src/parrot.gleam @@ -139,7 +139,12 @@ fn cmd_gen(engine: sqlc.Engine, db: String) -> Result(Nil, errors.ParrotError) { |> spinner.start() let gen_result = - shellout.command(run: "./sqlc", with: ["generate"], in: sqlc_dir, opt: []) + shellout.command( + run: "./sqlc", + with: ["generate", "--file", "sqlc.json"], + in: sqlc_dir, + opt: [], + ) use _ <- given.error(gen_result, return: fn(err) { let #(_, err) = err From 20d96a487cb9edd4ee54e055f598c1a3776ebae9 Mon Sep 17 00:00:00 2001 From: Daniel Kurz Date: Fri, 10 Oct 2025 13:49:45 +0200 Subject: [PATCH 20/20] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c33fd53..65cbc9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Parrot now generates a `sqlc.json` instead of a yaml configuration. (https://github.com/daniellionel01/parrot/pull/71)
+ Thank you szelbi! (https://github.com/sz3lbi) + ## [2.1.1] - 2025-09-30 - Bug Fix: quotes are escaped properly in generated gleam code. (https://github.com/daniellionel01/parrot/pull/65)