#!/usr/bin/env ruby #$LOAD_PATH.unshift "/home/clr/prj/shellopts/lib" require 'shellopts' require 'pg_graph.rb' require "pg_graph/timer.rb" include ShellOpts SPEC = %( @ Load, dump, and clean databases pg_graph is a utility that uses the PgGraph module to load, dump, or clean a database. It uses a variety of formats that can be executed by psql(1) ('psql') or by the Postgres library exec() call ('exec', 'sql'). It can also read and write Yaml data ('yaml') that is useful for exchanging data with other programs Note that when you're loading or dumping data in one of the SQL formats, you're almost always better off using postgres' own tools (pg_dump/pg_restore/pg_sql), except in the special case when the data contains circular foreign-key constraints and you don't have superuser privileges (say what? FIXME) The dump command can also write the type of the database in yaml format. This is the default -m,meta=EFILE @ Load meta data from YAML file Make pg_graph loads its meta data in YAML format from FILE instead of querying the database -M,marshal=EFILE @ Load meta data from marshalled file Make pg_graph loads its meta data from a marshalled PgMeta object instead of quering the database +i,ignore=SCHEMA Exclude objects in the given schema. This option can be repeated -r,reflections=EFILE Load reflections from FILE -t,reflections-table=TABLE_UID? Read/write reflections in the given table. The table argument can be both a full UID (eg. "my_schema.my_reflections") or just a table name in the public schema. Default is "public.reflections". The table and schema is created if not present. If this option is used together with the --reflections option, the reflections are read from th e given file and written back to the database. If both options are missing, pg_graph will look for the default table and load it if present -f,format=FORMAT:sql,exec,psql,yaml Input/output format. Can be one of 'sql', 'exec', 'psql', or 'yaml' (default) The 'psql' format is meant to be fed to the psql(1) command and contains psql(1) meta-commands to silence the output and to terminate on any error. The 'sql' format expects the database triggers to have been disabled beforehand while the 'exec' format includes statements to disble triggers. The 'exec' format also resets serials The yaml format only contains the data and can be loaded by pg_graph. Triggers will be disabled while loading and ID's restored afterwards -k,kind=KIND:meta,type,data Output kind. Can be one of 'meta', 'type' (the default), or 'data' --time Emit timings for process load! -- DATABASE [FILE] Loads data into the database. The file format is determined by the file's extension but can also be set explicitly using the --format option. Reads from standard input if FILE is missing dump! -- DATABASE Dumps data on standard output. Default is to dump the type system in yaml format but this can be explicitly set using the --kind and --format options clean! -- DATABASE Cleans the database by emptying all tables ) # Returns a connection/type tuple # def load_type(timer, opts, database) tg = timer.group("initialization") conn = tg.time("connect") { PgConn.new(database) if !opts.meta? && !opts.marshal? } meta = tg.time("meta") { if opts.meta? PgMeta.load_file(opts.meta) elsif opts.marshal? PgMeta.load_marshal(opts.marshal) else PgMeta.new(conn) end } reflector = tg.time("reflector") { table, schema = opts.reflections_table&.split(".")&.reverse schema ||= PgGraph::Reflector::REFLECTIONS_SCHEMA table ||= PgGraph::Reflector::REFLECTIONS_TABLE if opts.reflections? r = PgGraph::Reflector.load_file(opts.reflections) r.save_table(conn, schema, table) if opts.reflections_table? r else if opts.reflections_table? PgGraph::Reflector.load_table(conn, schema, table) elsif conn.schema.exist_table?(schema, table) PgGraph::Reflector.load_table(conn, schema, table) else nil end end } type = timer.time("type") { PgGraph::Type.new(meta, reflector, ignore: opts.ignore) } [conn, type] end opts, args = ShellOpts::ShellOpts.process(SPEC, ARGV) timing = opts.time? timer = Timer::Timer.new case opts.subcommand || :dump! when :load! database = args.extract(1) file = args.expect(0..1) || "/dev/stdin" if opts.format? format = opts.format else format = case File.extname(file) when ".sql"; "sql" when ".yaml", ".yml"; "yaml" else "yaml" end end case format when "sql", "exec"; connection = timer.time("connect") { PgConn.new(database) } timer.time("load file") { connection.exec(IO.read(file)) } when "psql" timer.time("psql") { system "psql -d #{database} < #{file} >/dev/null" } when "yaml" connection, type = load_type(timer, opts, database) tg = timer.group("read data") data = tg.time("data") { PgGraph::Data.new(type, YAML.load(IO.read(file))) } tg = timer.group("write data") for label, sql in PgGraph::Data::SqlRender.new(data, :exec).to_h tg.time(label) { connection.exec(sql.join) } end end when :dump! database = args.expect(1) case opts.kind || "type" when "meta" connection = timer.time("connect") { PgConn.new(database) if !opts.meta? } meta = timer.time("meta") { opts.meta? ? PgMeta.load_file(opts.meta) : PgMeta.new(connection) } meta.dump when "type" connection, type = load_type(timer, opts, database) type.dump when "data" connection, type = load_type(timer, opts, database) data = timer.time("instantiate") { type.instantiate(connection) } timer.time("dump") { case opts.format || "yaml" when "sql"; puts data.to_sql when "exec"; puts data.to_exec_sql when "psql"; puts data.to_psql_sql when "yaml"; puts data.to_yaml.to_yaml end } end when :clean! database = args.expect(1) connection, type = load_type(timer, opts, database) tg = timer.group("data") data = tg.time("data") { type.instantiate } tg = timer.group("clean data") for label, sql in PgGraph::Data::SqlRender.new(data, :exec).to_h tg.time(label) { connection.exec(sql.join) } end else raise ArgumentError, "Case not matched" end timer.dump($stderr) if timing