| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 1 | load("@io_bazel_rules_go//go:def.bzl", "go_context") |
| 2 | |
| 3 | # _parse_migrations takes a list of golang-migrate compatible schema files (in |
| 4 | # the format <timestamp>_some_description_{up,down}.sql) and splits them into |
| 5 | # 'up' and 'down' dictionaries, each a map from timestamp to underlying file. |
| 6 | # |
| 7 | # It also does some checks on the provided file names, making sure that |
| 8 | # golang-migrate will parse them correctly. |
| 9 | def _parse_migrations(files): |
| 10 | uppers = {} |
| 11 | downers = {} |
| 12 | |
| 13 | # Ensure filename fits golang-migrate format, sort into 'up' and 'down' files. |
| 14 | for file in files: |
| 15 | if not file.basename.endswith(".up.sql") and not file.basename.endswith(".down.sql"): |
| 16 | fail("migration %s must end woth .{up,down}.sql" % file.basename) |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 17 | if len(file.basename.split(".")) != 3: |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 18 | fail("migration %s must not contain any . other than in .{up,down}.sql extension" % file.basename) |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 19 | first = file.basename.split(".")[0] |
| 20 | if len(first.split("_")) < 2: |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 21 | fail("migration %s must be in <timestamp>_<name>.{up,down}.sql format" % file.basename) |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 22 | timestamp = first.split("_")[0] |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 23 | if not timestamp.isdigit(): |
| 24 | fail("migration %s must be in <timestamp>_<name>.{up,down}.sql format" % file.basename) |
| 25 | timestamp = int(timestamp) |
| 26 | if timestamp < 1662136250: |
| 27 | fail("migration %s must be in <timestamp>_<name>.{up,down}.sql format" % file.basename) |
| 28 | |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 29 | if file.basename.endswith(".up.sql"): |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 30 | if timestamp in uppers: |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 31 | fail("migration %s conflicts with %s" % [file.basename, uppers[timestamp].basename]) |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 32 | uppers[timestamp] = file |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 33 | if file.basename.endswith(".down.sql"): |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 34 | if timestamp in downers: |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 35 | fail("migration %s conflicts with %s" % [file.basename, downers[timestamp].basename]) |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 36 | downers[timestamp] = file |
| 37 | |
| 38 | # Check each 'up' has a corresponding 'down', and vice-versa. |
| 39 | for timestamp, up in uppers.items(): |
| 40 | if timestamp not in downers: |
| 41 | fail("%s has no corresponding 'down' migration" % up.basename) |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 42 | if downers[timestamp].basename.replace("down.sql", "up.sql") != up.basename: |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 43 | fail("%s has no corresponding 'down' migration" % up.basename) |
| 44 | for timestamp, down in downers.items(): |
| 45 | if timestamp not in uppers: |
| 46 | fail("%s has no corresponding 'up' migration" % down.basename) |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 47 | if uppers[timestamp].basename.replace("up.sql", "down.sql") != down.basename: |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 48 | fail("%s has no corresponding 'up' migration" % down.basename) |
| 49 | |
| 50 | return uppers, downers |
| 51 | |
| 52 | def _sqlc_go_library(ctx): |
| 53 | go = go_context(ctx) |
| 54 | |
| 55 | importpath_parts = ctx.attr.importpath.split("/") |
| 56 | package_name = importpath_parts[-1] |
| 57 | |
| Tim Windelschmidt | bdd0d25 | 2025-01-09 22:31:08 +0100 | [diff] [blame^] | 58 | # Split migrations into 'up' and 'down'. Only pass 'up' to sqlc. |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 59 | uppers, _ = _parse_migrations(ctx.files.migrations) |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 60 | |
| 61 | # Make sure given queries have no repeating basenames. This ensures clean |
| 62 | # mapping source SQL file name and generated Go file. |
| 63 | query_basenames = [] |
| 64 | for query in ctx.files.queries: |
| 65 | if query.basename in query_basenames: |
| 66 | fail("duplicate %s base name in query files" % query.basename) |
| 67 | query_basenames.append(query.basename) |
| 68 | |
| 69 | # Go files generated by sqlc. |
| 70 | sqlc_go_sources = [ |
| 71 | # db.go and models.go always exist. |
| 72 | ctx.actions.declare_file("db.go"), |
| 73 | ctx.actions.declare_file("models.go"), |
| 74 | ] |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 75 | |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 76 | # For every query file, basename.go is also generated. |
| 77 | for basename in query_basenames: |
| 78 | sqlc_go_sources.append(ctx.actions.declare_file(basename + ".go")) |
| 79 | |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 80 | # Cockroachdb is PostgreSQL with some extra overrides to fix Go/SQL type |
| 81 | # mappings. |
| 82 | overrides = [] |
| 83 | if ctx.attr.dialect == "cockroachdb": |
| 84 | overrides = [ |
| 85 | # INT is 64-bit in cockroachdb (32-bit in postgres). |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 86 | {"go_type": "int64", "db_type": "pg_catalog.int4"}, |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 87 | ] |
| 88 | |
| 89 | config = ctx.actions.declare_file("_config.yaml") |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 90 | |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 91 | # All paths in config are relative to the config file. However, Bazel paths |
| 92 | # are relative to the execution root/CWD. To make things work regardless of |
| 93 | # config file placement, we prepend all config paths with a `../../ ...` |
| 94 | # path walk that makes the path be execroot relative again. |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 95 | config_walk = "../" * config.path.count("/") |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 96 | config_data = json.encode({ |
| 97 | "version": 2, |
| 98 | "sql": [ |
| 99 | { |
| 100 | "schema": [config_walk + up.path for up in uppers.values()], |
| 101 | "queries": [config_walk + query.path for query in ctx.files.queries], |
| 102 | "engine": "postgresql", |
| 103 | "gen": { |
| 104 | "go": { |
| 105 | "package": package_name, |
| 106 | "out": config_walk + sqlc_go_sources[0].dirname, |
| 107 | "overrides": overrides, |
| 108 | }, |
| 109 | }, |
| 110 | }, |
| 111 | ], |
| 112 | }) |
| 113 | ctx.actions.write(config, config_data) |
| 114 | |
| 115 | # Generate types/functions using sqlc. |
| 116 | ctx.actions.run( |
| 117 | mnemonic = "SqlcGen", |
| 118 | executable = ctx.executable._sqlc, |
| 119 | arguments = [ |
| 120 | "generate", |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 121 | "-f", |
| 122 | config.path, |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 123 | ], |
| 124 | inputs = [ |
| Tim Windelschmidt | 156248b | 2025-01-10 00:27:45 +0100 | [diff] [blame] | 125 | config, |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 126 | ] + uppers.values() + ctx.files.queries, |
| 127 | outputs = sqlc_go_sources, |
| 128 | ) |
| 129 | |
| Serge Bazanski | 9cdec58 | 2022-09-15 18:48:27 +0200 | [diff] [blame] | 130 | library = go.new_library(go, srcs = sqlc_go_sources, importparth = ctx.attr.importpath) |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 131 | source = go.library_to_source(go, ctx.attr, library, ctx.coverage_instrumented()) |
| 132 | return [ |
| 133 | library, |
| 134 | source, |
| 135 | OutputGroupInfo(go_generated_srcs = depset(library.srcs)), |
| 136 | ] |
| 137 | |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 138 | sqlc_go_library = rule( |
| 139 | implementation = _sqlc_go_library, |
| 140 | attrs = { |
| 141 | "migrations": attr.label_list( |
| 142 | allow_files = True, |
| 143 | ), |
| 144 | "queries": attr.label_list( |
| 145 | allow_files = True, |
| 146 | ), |
| 147 | "importpath": attr.string( |
| 148 | mandatory = True, |
| 149 | ), |
| 150 | "dialect": attr.string( |
| 151 | mandatory = True, |
| 152 | values = ["postgresql", "cockroachdb"], |
| 153 | ), |
| 154 | "_sqlc": attr.label( |
| Tim Windelschmidt | 3fdaeac | 2023-11-13 23:33:07 +0100 | [diff] [blame] | 155 | default = Label("@com_github_sqlc_dev_sqlc//cmd/sqlc"), |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 156 | allow_single_file = True, |
| 157 | executable = True, |
| 158 | cfg = "exec", |
| 159 | ), |
| Serge Bazanski | 1b28e1b | 2022-09-05 18:41:18 +0200 | [diff] [blame] | 160 | }, |
| 161 | toolchains = ["@io_bazel_rules_go//go:toolchain"], |
| 162 | ) |