#!/usr/bin/env ruby $t0 = Time.now require 'fileutils' require 'fixture_fox.rb' require 'shellopts' require "pg_graph/timer.rb" include ShellOpts # TODO: Add options for meta and type SPEC = %( @ Generate test data for Postgres databases Read a fox file and generate data. The data are written on standard output in various formats or they can be loaded directly into the database -r,reflections=EFILE Reflections YML file -s,state=EFILE? State file. fox(1) reads ids and anchors from this file. Default is fox.yml -w,write=FILE? @ Write new state file Write the new state to FILE or back to the input state file if FILE is not given -d,delete=KIND:touched,recursive,all,none Controls the delete statements. 'touched' deletes only from tables that are specified in the fox file, 'recursive' deletes from touched tables and their dependent tables, and 'all' (the default) deletes from all tables in the database. If 'none' is given, no delete statements are emitted. Records included in the state file are never deleted -f,format=FORMAT:sql,exec,psql,yaml Controls the format of the 'data' output type. FORMAT can be one of 'sql', 'exec', 'psql' (the default), or 'yaml'. The format option can also be used together with the --dump option for some of the dump types -x,exec Execute the generated SQL instead of printing it on standard output +e,exclude=SCHEMA, Exclude the given schemas. The schemas are passed on to PgGraph to exclude schemas that doesn't comply with PgGraph's naming conventions, typically FDW schemas. This option can be repeated -t,time Emit timings for sub-processes --dump=KIND:tokens,meta,type,ast,idr,state,anchors,ids Dump the given data on standard output. KIND can be one of 'tokens', 'meta', 'type', 'ast', 'idr', 'state', 'anchors', or 'ids'. This option is mostly used for debug -- DATABASE [FOX-FILE...] ) DEFAULT_STATE_FILE = "fox-state.fox" class Break < RuntimeError; end begin opts, args = ShellOpts.process(SPEC, ARGV, exception: true) timing = opts.time? timer = Timer:: Timer.new opts.format ||= 'psql' if opts.state? opts.state = File.expand_path(opts.state || DEFAULT_STATE_FILE) FileUtils.touch(opts.state) if !File.exist?(opts.state) end db = args.extract(1) files = args.empty? ? %w(/dev/stdin) : args files.each { |path| File.readable?(path) or raise ShellOpts::Error.new(nil), "Can't read '#{path}'" } tg = timer.group("initialization") # Connect to postgres begin conn = tg.time("connect") { PgConn.new(db) } rescue PG::ConnectionBad raise ShellOpts::Error.new(nil), "Can't connect to database '#{db}'" end # Load meta object meta = tg.time("meta") { PgMeta.new(conn) } if opts.dump == "meta" if opts.format == "yaml" puts meta.to_yaml else meta.dump end exit end # Load reflections if opts.reflections? reflector = tg.time("reflections") { PgGraph::Reflector.load_yaml(YAML.load(File.read(opts.reflections))) } else reflector = tg.time("reflections") { PgGraph::Reflector.new } end # Load types type = tg.time("type") { PgGraph::Type.new(meta, reflector, ignore: opts.exclude || []) } # Dump types? if opts.dump == "type" type.dump exit end # Load state tg = timer.group("compiling") ids, anchors, fox = nil, nil, nil # Enter into namespace tg.time("state") { ids, anchors = opts.state? ? FixtureFox::Fox.read_state(type, opts.state) : [nil, nil] } # Create fox object tg.time("initialize") { fox = FixtureFox::Fox.new(type, ids: ids, anchors: anchors) } for file in files source, lines = tg.time("tokenize") { fox.tokenize(file) } tg.time("parse") { fox.parse(source, lines) } end if opts.dump == "tokens" fox.lines.each { |l| puts l.to_s(long: true) } exit end # Analyze tga = tg.group("analyze") tga.time("assign types") { fox.assign_types } tga.time("check types") { fox.check_types } tg.time("generate") { fox.generate } if opts.dump? case opts.dump || "data" # when "tokens"; # Handled above # when "meta"; # Handled above # when "type"; # Handled above when "ast"; fox.ast.dump when "state"; FixtureFox::Fox.write_state(ids, anchors, "/dev/stdout") when "idr"; fox.idr.dump when "anchors"; FixtureFox::Fox.write_anchors(fox.anchors, "/dev/stdout") when "ids"; FixtureFox::Fox.write_ids(fox.ids, "/dev/stdout") end exit end if opts.exec? tg.time("write") { fox.data.write(conn, ids: ids) } else tg.time("emit") { # puts "Format: #{opts.format.inspect}" # exit if opts.format == "yaml" puts fox.to_yaml else puts fox.to_sql(format: opts.format&.to_sym || :psql, ids: ids, delete: opts.delete&.to_sym || :all) end } end # Save state if opts.write? FixtureFox::Fox.write_state(fox.ids, fox.anchors, opts.write || opts.state) end rescue FixtureFox::Error => ex $stderr.puts ex.backtrace.join("\n") $stderr.puts $stderr.puts ex.error_message $stderr.puts $error = true exit 1 ensure timer.dump($stderr) if timing && !$error end