Line data Source code
1 : use std::path::PathBuf;
2 : use std::str::FromStr;
3 :
4 : use anyhow::*;
5 : use clap::{Arg, ArgMatches, Command, value_parser};
6 : use postgres::Client;
7 : use postgres_ffi::PgMajorVersion;
8 : use wal_craft::*;
9 :
10 0 : fn main() -> Result<()> {
11 0 : env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("wal_craft=info"))
12 0 : .init();
13 0 : let arg_matches = cli().get_matches();
14 :
15 0 : let wal_craft = |arg_matches: &ArgMatches, client: &mut Client| {
16 0 : let intermediate_lsns = match arg_matches
17 0 : .get_one::<String>("type")
18 0 : .map(|s| s.as_str())
19 0 : .context("'type' is required")?
20 : {
21 0 : Simple::NAME => Simple::craft(client)?,
22 0 : LastWalRecordXlogSwitch::NAME => LastWalRecordXlogSwitch::craft(client)?,
23 0 : LastWalRecordXlogSwitchEndsOnPageBoundary::NAME => {
24 0 : LastWalRecordXlogSwitchEndsOnPageBoundary::craft(client)?
25 : }
26 0 : WalRecordCrossingSegmentFollowedBySmallOne::NAME => {
27 0 : WalRecordCrossingSegmentFollowedBySmallOne::craft(client)?
28 : }
29 0 : LastWalRecordCrossingSegment::NAME => LastWalRecordCrossingSegment::craft(client)?,
30 0 : a => panic!("Unknown --type argument: {a}"),
31 : };
32 0 : let end_of_wal_lsn = client.pg_current_wal_insert_lsn()?;
33 0 : for lsn in intermediate_lsns {
34 0 : println!("intermediate_lsn = {lsn}");
35 0 : }
36 0 : println!("end_of_wal = {end_of_wal_lsn}");
37 0 : Ok(())
38 0 : };
39 :
40 0 : match arg_matches.subcommand() {
41 0 : None => panic!("No subcommand provided"),
42 0 : Some(("print-postgres-config", _)) => {
43 0 : for cfg in REQUIRED_POSTGRES_CONFIG.iter() {
44 0 : println!("{cfg}");
45 0 : }
46 0 : Ok(())
47 : }
48 :
49 0 : Some(("with-initdb", arg_matches)) => {
50 0 : let cfg = Conf {
51 0 : pg_version: *arg_matches
52 0 : .get_one::<PgMajorVersion>("pg-version")
53 0 : .context("'pg-version' is required")?,
54 0 : pg_distrib_dir: arg_matches
55 0 : .get_one::<PathBuf>("pg-distrib-dir")
56 0 : .context("'pg-distrib-dir' is required")?
57 0 : .to_owned(),
58 0 : datadir: arg_matches
59 0 : .get_one::<PathBuf>("datadir")
60 0 : .context("'datadir' is required")?
61 0 : .to_owned(),
62 : };
63 0 : cfg.initdb()?;
64 0 : let srv = cfg.start_server()?;
65 0 : wal_craft(arg_matches, &mut srv.connect_with_timeout()?)?;
66 0 : srv.kill();
67 0 : Ok(())
68 : }
69 0 : Some(("in-existing", arg_matches)) => wal_craft(
70 0 : arg_matches,
71 0 : &mut postgres::Config::from_str(
72 0 : arg_matches
73 0 : .get_one::<String>("connection")
74 0 : .context("'connection' is required")?,
75 : )
76 0 : .context(
77 : "'connection' argument value could not be parsed as a postgres connection string",
78 0 : )?
79 0 : .connect(postgres::NoTls)?,
80 : ),
81 0 : Some(_) => panic!("Unknown subcommand"),
82 : }
83 0 : }
84 :
85 1 : fn cli() -> Command {
86 1 : let type_arg = &Arg::new("type")
87 1 : .help("Type of WAL to craft")
88 1 : .value_parser([
89 1 : Simple::NAME,
90 1 : LastWalRecordXlogSwitch::NAME,
91 1 : LastWalRecordXlogSwitchEndsOnPageBoundary::NAME,
92 1 : WalRecordCrossingSegmentFollowedBySmallOne::NAME,
93 1 : LastWalRecordCrossingSegment::NAME,
94 1 : ])
95 1 : .required(true);
96 :
97 1 : Command::new("Postgres WAL crafter")
98 1 : .about("Crafts Postgres databases with specific WAL properties")
99 1 : .subcommand(
100 1 : Command::new("print-postgres-config")
101 1 : .about("Print the configuration required for PostgreSQL server before running this script")
102 : )
103 1 : .subcommand(
104 1 : Command::new("with-initdb")
105 1 : .about("Craft WAL in a new data directory first initialized with initdb")
106 1 : .arg(type_arg)
107 1 : .arg(
108 1 : Arg::new("datadir")
109 1 : .help("Data directory for the Postgres server")
110 1 : .value_parser(value_parser!(PathBuf))
111 1 : .required(true)
112 : )
113 1 : .arg(
114 1 : Arg::new("pg-distrib-dir")
115 1 : .long("pg-distrib-dir")
116 1 : .value_parser(value_parser!(PathBuf))
117 1 : .help("Directory with Postgres distributions (bin and lib directories, e.g. pg_install containing subpath `v14/bin/postgresql`)")
118 1 : .default_value("/usr/local")
119 : )
120 1 : .arg(
121 1 : Arg::new("pg-version")
122 1 : .long("pg-version")
123 1 : .help("Postgres version to use for the initial tenant")
124 1 : .value_parser(value_parser!(u32))
125 1 : .required(true)
126 :
127 : )
128 : )
129 1 : .subcommand(
130 1 : Command::new("in-existing")
131 1 : .about("Craft WAL at an existing recently created Postgres database. Note that server may append new WAL entries on shutdown.")
132 1 : .arg(type_arg)
133 1 : .arg(
134 1 : Arg::new("connection")
135 1 : .help("Connection string to the Postgres database to populate")
136 1 : .required(true)
137 : )
138 : )
139 1 : }
140 :
141 : #[test]
142 1 : fn verify_cli() {
143 1 : cli().debug_assert();
144 1 : }
|