Module: Prick::SubCommand

Defined in:
lib/prick-command.rb,
lib/prick/subcommand/prick-fox.rb,
lib/prick/subcommand/prick-run.rb,
lib/prick/subcommand/prick-set.rb,
lib/prick/subcommand/prick-bash.rb,
lib/prick/subcommand/prick-drop.rb,
lib/prick/subcommand/prick-init.rb,
lib/prick/subcommand/prick-list.rb,
lib/prick/subcommand/prick-make.rb,
lib/prick/subcommand/subcommand.rb,
lib/prick/subcommand/prick-build.rb,
lib/prick/subcommand/prick-clean.rb,
lib/prick/subcommand/prick-setup.rb,
lib/prick/subcommand/prick-touch.rb,
lib/prick/subcommand/prick-create.rb,
lib/prick/subcommand/prick-migrate.rb,
lib/prick/subcommand/prick-release.rb,
lib/prick/subcommand/prick-snapshot.rb,
lib/prick/subcommand/prick-teardown.rb

Constant Summary collapse

PRICK_BUILD_FILES =

FIXME FIXME FIXME HARDCODED

%w(tables.sql views.sql functions.sql)

Class Method Summary collapse

Class Method Details

.bash(main: false) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/prick/subcommand/prick-bash.rb', line 18

def self.bash(main: false)
#   IO.capture {
    env = Prick.state.environment # Shorthands
    envs = Prick.state.environments

    puts "#!/usr/bin/bash"
    puts
    puts "# This file is auto-generated by prick. Please don't touch"
    puts
    puts ". bash.include"
    puts

    # Emit environment. PRICK_ENVIRONMENT_BUILD is excluded because its value
    # is bash source that is hard to escape and there is no use for this
    # variable anyway
    puts "### ENVIRONMENT by state.rb"
    puts
    puts Prick.state.bash_source(scope: :global)
    puts

    # Emit script
    if env
      envs.bash_command env
    else
      envs.bash_command
    end

    if main
      puts "### MAIN by prick-load.rb"
      puts
      puts "build"
    end
#   }
end

.build(database, username, schema, builddir: Prick.state.schema_dir, force: false, step: false, timer: nil, dump: nil) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/prick/subcommand/prick-build.rb', line 6

def self.build(
    database, username, schema,
    builddir: Prick.state.schema_dir,
    force: false, # Build all schemas
    step: false, timer: nil, dump: nil)

  Timer.on! if timer

  Timer.time "Prick::Command#build" do
    begin
      super_conn = State.connection # Used to create new databases (doesn't make a
                                    # difference right now as the database user is
                                    # a superuser anyway
      conn = nil
      builder = nil

      constrain super_conn.rdbms.exist?(database), true # FIXME Same problem as below

      Timer.time "Load build object" do
        if super_conn.rdbms.exist? database # FIXME Why create database? Setup should have done this
          exist = true
        else
          super_conn.rdbms.create database, owner: username
          exist = false
        end
        conn = Prick.state.connection

        builder = Prick::Build::Builder.new(conn, builddir, step: step)

        if exist # Empty (part of) the database
          if force
            # Drop all schemas and re-create the public schema. The prick
            # schema is only dropped if :force is true
#             builder.pool.delete_schema("prick") if super_conn.schema.exist?("prick") && !force
#             super_conn.rdbms.empty!(database, public: false, exclude: (force ? [] : ["prick"]))

            # Drop all schemas except the prick schema and re-creates the
            # public schema
#             builder.pool.delete_schema("prick") if super_conn.schema.exist?("prick")
#             super_conn.rdbms.empty!(database, public: false, exclude: ["prick"])
            super_conn.rdbms.empty!(database, public: false)
            Prick::SubCommand::init_database(database, username)
          else
            # Find all schemas
            refresh_schemas = conn.schema.list

            # Find existing keep-schemas (but exclude target schema)
            keep_schemas = builder.keep_schemas.select { |s|
              conn.schema.exist?(s) && (schema.nil? || schema != s)
            }

            # Remove keep-schemas from list of schemas
            refresh_schemas -= keep_schemas

            # Also remove keep-schemas from the build pool. TODO Why don't we
            # use the pool tracker's list of keep schemas?
            builder.pool.delete_schema(keep_schemas)

            # Drop refresh schemes
            refresh_schemas.each { |s| conn.schema.drop(s, cascade: true) }
          end
        end

        # Delete schemas that comes after the target in the build sequence if
        # 'prick build' was given a schema
        builder.pool.delete_schema(builder.pool.after_schema(schema)) if schema
      end

      case dump
        when :nodes; builder.nodes.reject { |node| node.is_a?(Build::BuildNode) }.map &:dump
        when :allnodes; builder.nodes.map &:dump
        when :batches; builder.dump
        when nil;
      else
        Prick.error "Illegal dump type: #{dump.inspect}"
      end && exit

      Timer.time "Execute build object" do
        builder.execute conn
      end

#     rescue Prick::Error => ex
#       $stderr.puts ex.message
#       exit 1

    rescue ::Command::Error => ex
      $stderr.puts ex.message
      exit 1

    ensure
      super_conn&.terminate
      conn&.terminate
    end
  end
end

.clean(database, exclude: %w(prick))) ⇒ Object

Drop users and hollow-out database. This has the same as effect as teardown+setup but without recreating the database owner and the database which would otherwise terminate all user sessions



9
10
11
12
13
14
# File 'lib/prick/subcommand/prick-clean.rb', line 9

def self.clean(database, exclude: %w(prick))
  Prick::SubCommand.drop_users(database)
  State.connection { |conn|
    conn.rdbms.empty!(database, exclude: exclude) if conn.rdbms.exist?(database)
  }
end

.create_database(database, username, environment) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/prick/subcommand/prick-create.rb', line 9

def self.create_database(database, username, environment)
  super_conn = State.connection

  # Create user if absent
  if !super_conn.role.exist? username
    # FIXME Should not be created as superuser but we can't do that before we
    # have a super: option in build so that superuser-requiring function
    # definitions can be built
    super_conn.role.create username, superuser: true, can_login: true, create_role: true
  end

  # Create database
  super_conn.rdbms.create database, owner: username

  # Initialize prick schema
  init_database database, username, environment
end

.create_migration(username, from_version, force: false, file: nil) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/prick/subcommand/prick-create.rb', line 50

def self.create_migration(username, from_version, force: false, file: nil)
  constrain from_version, PrickVersion
  Git.clean? or raise "Won't migrate: Repository is dirty"
  Git.synchronized? or raise "Won't migrate: Repository is not synchronized"

  from_version != PrickVersion.zero or Prick.error "Can't migrate from release 0.0.0"
  to_version = Prick.state.version
  migration_dir = "#{MIGRATION_DIR}/#{from_version}"
  migration_exist = File.directory? migration_dir
  force || file || !migration_exist or Prick.failure "Migration #{from_version} exists"

  $quiet = true if file

  if file && migration_exist
    File.open(file, "w") { |file|
      for diff_file in DIFF_FILES
        file.write File.read("#{migration_dir}/#{diff_file}")
      end
    }
  else
    from_version != to_version or Prick.failure "Can't migrate to same release"
    from_version < to_version or Prick.failure "Can't migrate backwards (why not?)"

    diff = nil
    if force || !migration_exist
      from_db = "#{Prick.state.name}-#{from_version}"
      to_db = "#{Prick.state.name}-#{to_version}"

      mesg "Migrating from #{from_version} to #{to_version}"
      begin
        origin = Git.origin

        # Local repos are supported to ease testing
        if File.directory?(origin)
          origin = File.expand_path(origin)
        end

        diff = Dir.mktmpdir { |tmpdir|
            Dir.chdir(tmpdir) {
            mesg "  Building #{from_db}"
            Git.clone(origin, from_version, branch: from_version)
            Dir.chdir(from_version.to_s) { build(from_db, username, nil) }
          }

          mesg "  Building #{to_db}"
          build(to_db, username, nil)

          mesg "  Creating diff"
          Diff.new(from_db, to_db)
        }
        !diff.same? or Prick.failure "No changes"
      ensure
        drop_all(from_db) # FIXME This will fail, maybe use teardown instead?
        drop_all(to_db)
      end
    end

    diff.write(file) if file
  end

  if file
    File.open(file, "a") { |f|
      f.puts "-- UPDATE VERSION"
      f.puts File.readlines(SCHEMA_VERSION_PATH).grep_v(/^--/)
    }
  else
    FileUtils.rm_rf migration_dir if force
    FileUtils.mkdir_p migration_dir
    Command.command "cp -a #{SHARE_PATH}/migrate/migration/. #{migration_dir}"
    Dir.chdir(migration_dir) { diff.write(*DIFF_FILES, mark: true) }
  end
end

.drop_data(database, schemas = nil) ⇒ Object

Drop data not in snapshot



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/prick/subcommand/prick-drop.rb', line 46

def self.drop_data(database, schemas = nil)
  conn = Prick.state.conn
  conn.session.triggers(false) {
    if schemas.nil?
      builder = Prick::Build::Builder.new(conn, Prick.state.schema_dir)
      pool = builder.pool
      schemas = pool.refresh_schemas
    end
    schema_list = conn.quote_values(schemas)
    schema_expr = schemas ? "true = true" : "schema_name in (#{schema_list})"
    tables = conn.tuples %(
      select schema_name, table_name, max_id
      from prick.snapshots
      where #{schema_expr}
    )
    tables.each { |schema, table, max_id|
      uid = "#{schema}.#{table}"
      if max_id
        conn.exec "delete from #{uid} where id > #{max_id}"
        conn.schema.set_serial(schema, table, max_id) if conn.schema.sequence(schema, table)
      else
        conn.exec "delete from #{uid}"
        conn.schema.set_serial(schema, table, nil) if conn.schema.sequence(schema, table)
      end
    }
  }
end

.drop_database(database) ⇒ Object

Drop a database



40
41
42
43
# File 'lib/prick/subcommand/prick-drop.rb', line 40

def self.drop_database(database)
  State.connection.session.terminate(database, nil)
  State.connection { |conn| conn.rdbms.drop database }
end

.drop_owner(username) ⇒ Object

Drop database owner



7
8
9
10
11
12
13
# File 'lib/prick/subcommand/prick-drop.rb', line 7

def self.drop_owner(username)
  # No database at this point so no session is running and no cascade. A nop
  # if the user is the current user or owns objects in other databases
  if username != ENV['USER']
    super_conn.role.drop(username, fail: false, silent: true)
  end
end

.drop_schema(database, schemas = []) ⇒ Object

Empty the database



75
76
77
78
79
80
81
82
83
84
85
# File 'lib/prick/subcommand/prick-drop.rb', line 75

def self.drop_schema(database, schemas = [])
  constrain database, String
  constrain schemas, [String]
  if schemas.empty?
    State.connection { |conn| conn.rdbms.empty!(database, exclude: "prick") }
  else
    Prick.state.connection { |conn|
      schemas.each { |schema| conn.schema.drop(schema, cascade: true) }
    }
  end
end

.drop_users(database) ⇒ Object

Drop all users associated with the given database except the owner. We assume that users can only be connected to the project database and that they don’t own objects in other databases



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/prick/subcommand/prick-drop.rb', line 18

def self.drop_users(database)
  constrain database, String
  conn = super_conn
  if conn.rdbms.exist? database
    users = conn.role.list(database: database).reject { _1 == conn.rdbms.owner(database) }
    conn.session.terminate(database, users)
    if conn.rdbms.exist?(database)
      # Connect to database to make cascade work. Run in a block to close connection afterwards
      PgConn.new(database) { |conn| conn.role.drop(users, cascade: true) }
    else
      conn.rdbms.role.drop(users)
    end

  else
    # We don't terminate sessions because we assume one-database-users
    # FIXME: users is undefined
    raise NotImpementedError
    conn.role.drop(users, cascade: true) # Fails if the users owns objects in other databases
  end
end

.exist_database?(database) ⇒ Boolean

Returns:

  • (Boolean)


5
6
7
# File 'lib/prick/subcommand/prick-create.rb', line 5

def self.exist_database?(database)
  State.connection.rdbms.exist? database
end

.fox(database, username, files, builddir: Prick.state.schema_dir, reset: false, exclude: nil) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/prick/subcommand/prick-fox.rb', line 6

def self.fox(database, username, files, builddir: Prick.state.schema_dir, reset: false, exclude: nil)
  self.drop_data(database) if reset # In prick-drop
  conn = Prick.state.connection
  builder = Prick::Build::Builder.new(conn, builddir)
  exclude = (exclude || []) + builder.pg_graph_ignore_schemas
  opts = {
    state: FOX_STATE_PATH,
    delete: "none",
    exec: true,
    exclude: (exclude ? exclude.join(',') : nil),
    reflections: Prick.state.reflections_file
  }.reject { |k,v| v.nil? }.map { |k,v| "--#{k}#{v == true ? "" : "=#{v}"}" }.join(" ")
  Command.command "fox #{opts} #{database} #{files.join(" ")}"
end

.init(project_file, dir, name, title) ⇒ Object

FIXME: Ignores -p, -e, -s, -f options



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/prick/subcommand/prick-init.rb', line 5

def self.init(project_file, dir, name, title) # dir, name, and title can be nil
  cwd = Dir.getwd
  if dir
    !File.exist?(dir) or Prick.error "Directory #{dir} exists"
    FileUtils.mkdir_p(dir)
    Dir.chdir(dir)
  else
    dir = "."
  end
  dirname = File.basename(Dir.getwd)
  name ||= dirname
  title ||= name.gsub(/[_-]/, " ").capitalize

  # Note that the initial project file is invalid and is removed again after
  # the initial commit
  Command.command %(
    git init .
    dir=#{SHARE_PATH}/init
    for path in $dir/*; do
      source_file=$(basename $path)
      dest_file=$(sed 's/^dot\././' <<<$source_file)
      cp -a $dir/$source_file $dest_file
    done
    git add .
    git commit -am "Initial import"
    rm -f #{project_file}
  ), fail: false
  Command.status == 0 or Prick.failure "Failed creating initial import"

  # Write (valid) configuration file
  state = State.new(project_file, nil, nil, nil, nil)
  state.name = name
  state.title = title
  state.prick_version = PrickVersion.new VERSION
  state.version = PrickVersion.new("0.0.0")
  state.save_project

  # Commit configuration file and create initial release
  Command.command %(
    set -e
    git add #{project_file}
    git commit -am "Release 0.0.0"
    git tag --message "Initial Release" v0.0.0
  ), fail: false
  Command.status == 0 or Prick.failure "Failed creating initial release"

  Dir.chdir(cwd)
  [dir, name]
end

.init_database(database, username, environment) ⇒ Object

Setup prick schema. Used when we rebuild the prick schema without disconnecting users



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/prick/subcommand/prick-create.rb', line 29

def self.init_database(database, username, environment)
  # Setup Prick schema. Note that this is hardcoded, the prick build file
  # is ignored
  conn = PgConn.new(database, username) # Can't use Prick.state.connection on a new database
  conn.schema.create("prick")
  PRICK_BUILD_FILES.each { |file| conn.exec(IO.read("#{SCHEMA_PRICK_DIR}/#{file}")) }

  # Add initial build record
  state = Prick.state # shorthand
  conn.insert "prick.builds",
      name: state.title,
      version: state.version,
      prick: state.prick_version,
      branch: state.branch,
      rev: state.rev(kind: :short),
      clean: state.clean?,
      environment: environment || Prick::DEFAULT_ENVIRONMENT,
      built_at: nil,
      success: nil
end

.list_databases(format: :long) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/prick/subcommand/prick-list.rb', line 37

def self.list_databases(format: :long)
  constrain format, :long, :short
  conn = State.connection
#   databases = conn.values "select datname from pg_database where datistemplate = false order by datname;"

  if format == :short
    puts Prick.databases
  else
    # Timestamp of newest file under git control
    newest_file = Command.command("ls -tr #{Git.list.join(" ")} | tail -1", stderr: false, fail: false).last
    fs_time = File.mtime(newest_file)

    git_branch = Git.branch.current
    git_rev = Git.id[0...8]

    rows = []
    current_database_row = nil
    Prick.databases { |database, conn|
      row = conn.record(%(
        select
            '#{database}' as "database", name, version, branch, rev, clean, environment,
            built_at, success
        from prick.versions
      ))

      # Detect if this is the current database
      if Prick.state.database == database
        is_current_database = true
        current_database_row = rows.size
      end

      if row[:success]
        clean = row[:clean]
        same_revision = row[:branch] == git_branch && row[:rev] == git_rev
        up2date = fs_time <= row[:built_at]

        # Set state
        row[:state] =
            case [clean, same_revision, up2date]
              in [true, true, true]; "clean"    # Built from clean repo
              in [true, true, false]; "ok"      # Clean repo but doesn't include latest uncommitted changes
              in [true, false, _]; "clean"      # Clean repo but different revision

              in [false, true, true]; "edge"    # Dirty repo, work-in-progress
              in [false, true, false]; "dirty"  # Dirty repo and doesn't include latest changes
              in [false, false, _]; "stale"     # Dirty repo, no way back
            end
      elsif row[:success] == false
        row[:state] = "fail"
      else
        row[:state] = "-"
      end

      # Convert built_at to string
      row[:built_at] = row[:built_at]&.strftime("%Y-%m-%d %H:%M")

      # Add current database marker
      last_column = (is_current_database ? "*" : "")

      rows << row.values + [last_column]
    }

    headers = %w(database project version branch rev clean environment built success state) + [" "]
    Fmt.puts_table(headers, rows, bold: current_database_row)
  end
end

.list_environments(format: :long) ⇒ Object



2
3
4
5
6
7
8
9
10
11
# File 'lib/prick/subcommand/prick-list.rb', line 2

def self.list_environments(format: :long)
  environments = Prick.state.environments.values.select { |env| env.comment }
  if format == :short
    puts environments.map(&:name)
  else
    headers = %w(Environment Description)
    rows = environments.map { |env| [env.name, env.comment] }
    Fmt.puts_table(headers, rows)
  end
end

.list_owners(format: :long) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/prick/subcommand/prick-list.rb', line 108

def self.list_owners(format: :long)
  owners = {}
  Prick.databases.each { |database|
#     owner = super_conn.rdbms.exist?(d) ? super_conn.rdbms.owner(d) : d
    owner = super_conn.rdbms.owner(database)
    (owners[owner] ||= []) << database
  }
  if format == :short
    puts owners.keys
  else
    headers = %w(username databases) + [" "]
    rows = owners.map { |owner, databases|
      current_user_mark = (Prick.state.username == owner ? '*' : "")
      [owner, databases.join(","), current_user_mark]
    }
    Fmt.puts_table(headers, rows)
  end
end

.list_usersObject



104
105
106
# File 'lib/prick/subcommand/prick-list.rb', line 104

def self.list_users
  puts State.connection.role.list(database: Prick.state.database)
end

.list_variables(format: :long, all: false) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/prick/subcommand/prick-list.rb', line 13

def self.list_variables(format: :long, all: false)
  if format == :short
    puts Prick.state.bash_environment(all: all).keys
  else
    headers = %w(variable value)
    vars = Prick.state.bash_environment(all: all).reject { |k,v| k == "PATH" }
    rows = vars.map
#     rows = vars.map { |k,v|
#       if v.is_a?(Array)
#         if v.first&.is_a?(Array)
#           v = v.first
#         else
#           v = v.join(" ")
#         end
#       else
#         v = v.to_s
#       end
#
#       [k, v]
#     }
    Fmt.puts_table(headers, rows)
  end
end

.make(database, username, schema, step: false, timer: nil, dump: nil) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/prick/subcommand/prick-make.rb', line 6

def self.make(database, username, schema, step: false, timer: nil, dump: nil)
  Timer.on! if timer
  Timer.time "Prick::Command#make" do
    begin
      super_conn = State.connection
      conn = nil
      builder = nil
      last_built_at = EPOCH
      clean = false
      create_schemas = []

      Timer.time "Load build object" do
        if super_conn.rdbms.exist? database
          conn = Prick.state.connection
          if conn.schema.exist_relation?("prick", "versions") && !conn.empty?("prick.versions")
            last_built_at = conn.value("select built_at from prick.versions")
          end
        else
          super_conn.rdbms.create database, owner: username
          conn = Prick.state.connection
          clean = true
        end

        builder = Prick::Build::Builder.new(conn, "schema", clean, step: step)

        if schema
          after_schemas = builder.pool.after_schema(schema)
          refresh_schemas = builder.pool.refresh_schemas
          conn.schema.drop(schema, cascade: true) # Marks it dirty

          # Nuke later schemas
          # FIXME Not a good idea when sitting on a leaf schema.
          # Forced-delete could be requested using 'prick make schema'
          #
          (after_schemas & refresh_schemas).each { |drop_schema|
            conn.schema.drop(drop_schema, cascade: true)
          }
          after_schemas.each { |delete_schema| builder.pool.delete_schema(delete_schema) }
        end

        touched_nodes = builder.nodes.select { |node| last_built_at.nil? || File.mtime(node.path) > last_built_at }
        touched_phases = touched_nodes.map(&:phase).uniq.compact
        touched_kinds = touched_nodes.map(&:kind).uniq.compact
        touched_schema = touched_nodes.first&.schema
        missing_schema = builder.schemas.find { |schema| !conn.schema.exist?(schema) }
        build_schema = builder.schemas.find { |schema| [touched_schema, missing_schema].include? schema }

        if build_schema.nil?
          puts "#{database} is up to date"
          exit
        end

        if touched_phases.include?(:decl) || touched_phases.empty?
          create_schemas = [build_schema] + builder.pool.after_schema(build_schema)
          create_schemas.each { |schema| conn.schema.drop(schema, cascade: true) }
          builder.pool.delete_schema(builder.pool.before_schema(build_schema), exclude: [:seed])
        elsif touched_phases.include?(:init) || touched_phases.include?(:term)
          builder.pool.clear(:decl, :seed)
        elsif touched_phases.include?(:seed)
          builder.pool.clear(:init, :decl, :term)
        end

#         builder.group
      end

      case dump
        when :nodes; builder.nodes.reject { |node| node.is_a?(Build::BuildNode) }.map &:dump
        when :allnodes; builder.nodes.map &:dump
        when :batches; builder.dump
        when nil;
      else
        Prick.error "Illegal dump type: #{dump.inspect}"
      end
      exit if dump

      Timer.time "Execute build object" do
        builder.execute(conn, create_schemas: create_schemas)
      end

    rescue Prick::Build::Error => ex
      $stderr.puts ex.message
      exit 1

    rescue Command::Error => ex
      $stderr.puts ex.message
      exit 1

    end
  end
end

.migrate(database, username, file: nil) ⇒ Object

def self.migrate(database, username, file) TODO



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/prick/subcommand/prick-migrate.rb', line 5

def self.migrate(database, username, file: nil)
  conn = Prick.state.connection
#     PgConn.new(database, username) { |conn|
    conn.schema.exist_relation? "prick", "versions" or 
        Prick.failure "Can't read version from database"

    from_version = PrickVersion.new(conn.value "select version from prick.versions") or
        Prick.failure "Illegal version in table prick.versions"

    if file
      conn.transaction {
        conn.exec File.read(file)
      }
    else
      to_version = Prick.state.version
      from_version != to_version or Prick.failure "Already up to date"
      from_version < to_version or Prick.failure "Can't migrate backwards"

      migration_dir = "#{MIGRATION_DIR}/#{from_version}" 
      File.directory? migration_dir or 
          Prick.failure "Can't migrate from #{from_version} to #{to_version}"

      puts "Migrating from #{from_version} to #{to_version}"

      builder = Prick::Build::Builder.new(conn, migration_dir)
      conn.transaction {
        builder.execute conn
        conn.exec File.read(SCHEMA_VERSION_PATH)
      }
    end
#     }
end

.owner_connObject



119
# File 'lib/prick-command.rb', line 119

def self.owner_conn = Prick.owner_conn

.release(kind) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/prick/subcommand/prick-release.rb', line 4

def self.release(kind)
  constrain kind, :major, :minor, :patch

  Git.clean? or raise "Won't release: Repository is dirty"
  Git.synchronized? or raise "Won't release: Repository is not synchronized with origin"

  version = Prick.state.version.increment!(kind).to_s
  Prick.state.save_project(overwrite: true)

  Git.add(Prick.state.project_file)
  Git.add(Prick.state.schema_file)
  Git.commit "Release #{version}"
  Git.tag.create "v#{version}"
  Git.branch.create version
  Git.push

  # TODO TODO TODO

  puts version
end

.run(database, username, path, step: false, timer: nil, dump: nil, schema: nil) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/prick/subcommand/prick-run.rb', line 6

def self.run(database, username, path, step: false, timer: nil, dump: nil, schema: nil)

  Timer.on! if timer

  Timer.time "Prick::Command#build" do
    begin
      super_conn = State.connection # Used to create new databases (doesn't make a
                                    # difference right now as the database user is
                                    # a superuser anyway
      conn = nil
      builder = nil

      constrain super_conn.rdbms.exist?(database), true # FIXME Same problem as below. Also in other commands

      Timer.time "Load build object" do
        if super_conn.rdbms.exist? database # FIXME Why create database? Setup should have done this
          exist = true
        else
          super_conn.rdbms.create database, owner: username
          exist = false
        end
        conn = Prick.state.connection

        # Parse
        builder = Prick::Build::Builder.new(conn, path, single: true, load_pool: false, step: step)

        # Register if a build file is referenced and normalize path to
        # include build.yml of directories
        if File.directory?(path)
          path = File.join(path, "build.yml")
          is_build_file = true
        elsif File.basename(path) == "build.yml"
          is_build_file = true
        else
          is_build_file = false
        end

        # Read schema from build file if possible and decide if schema should
        # be dropped beforehand
        if is_build_file
          if builder.root.has_schema
            !schema or Prick.error "Can't use --schema when doing a schema build"
            is_schema_rebuild = true
            schema = build_node.schema
          else
            is_schema_rebuild = false
          end
        else
          is_schema_rebuild = false
        end

        # Infer schema from path
        if !schema
          abspath = File.realpath(path)
          schemapath = File.realpath(Prick.state.schema_dir)
          if abspath =~ /^#{schemapath}\/([^\/]+)(?:\/.*)$/
            schema = $1
          else
            Prick.error "Can't find schema" # No default schema to avoid unintended runs
          end
        end

        # Write back schema to builder
        builder.root.schema = schema

        # Drop schema if needed
        if is_schema_rebuild
          conn.schema.drop schema, cascade: true
        end

        # Create schema if absent
        if !conn.schema.exist?(schema)
          conn.schema.create(schema)
        end
      end

      if dump
        builder.load_pool
        case dump
          when :nodes; builder.nodes.reject { |node| node.is_a?(Build::BuildNode) }.map &:dump
          when :allnodes; builder.nodes.map &:dump
          when :batches; builder.dump
        else
          Prick.error "Illegal dump type: #{dump.inspect}"
        end
      else
        Timer.time "Execute build object" do
          builder.execute conn
        end
      end

#     FIXME
#     rescue Prick::Error => ex
#       $stderr.puts ex.message
#       exit 1
#
#     rescue ::Command::Error => ex
#       $stderr.puts ex.message
#       exit 1

    ensure
      super_conn&.terminate
      conn&.terminate
    end
  end
end

.set(variable, value = nil, arg = nil) ⇒ Object



4
5
6
7
8
9
10
11
12
13
# File 'lib/prick/subcommand/prick-set.rb', line 4

def self.set(variable, value = nil, arg = nil)
  case variable
    when "database"; set_database(value, arg)
    when "environment"; set_environment(value)
    when "duration"; set_duration(value)
  else
    return false
  end
  true
end

.set_database(database, environment = nil) ⇒ Object

If the environment is nil, it is initialized from the database on next build



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/prick/subcommand/prick-set.rb', line 25

def self.set_database(database, environment = nil)
  if database
    conn = State.connection
    conn.rdbms.exist? database or Prick.error "Database '#{database}' does not exist"
    username = conn.rdbms.owner database

    Prick.state.database = database
    Prick.state.username = username

    if environment.nil?
      Prick.state.connection # Implicitly initialize database enviroment if present
      environment = Prick.state.database_environment # or Prick.error "Can't find environment"
    end
    Prick.state.environment = environment
    Prick.state.save_state
  else
    puts Prick.state.database
  end
end

.set_duration(make_start) ⇒ Object



45
46
47
48
49
50
# File 'lib/prick/subcommand/prick-set.rb', line 45

def self.set_duration(make_start)
  conn = Prick.state.connection
  built_at = conn.value "select built_at from prick.versions"
  duration = Time.now - Time.iso8601(make_start)
  conn.exec "update prick.versions set make_duration = #{duration}"
end

.set_environment(environment) ⇒ Object



15
16
17
18
19
20
21
22
# File 'lib/prick/subcommand/prick-set.rb', line 15

def self.set_environment(environment)
  if environment
    Prick.state.environment = environment
    Prick.state.save_state
  else
    puts Prick.state.environment
  end
end

.setup(database, username, environment) ⇒ Object



6
7
8
9
# File 'lib/prick/subcommand/prick-setup.rb', line 6

def self.setup(database, username, environment)
  create_database(database, username, environment)
  set_database(database, environment)
end

.snapshot(database, username, builddir: Prick.state.schema_dir) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/prick/subcommand/prick-snapshot.rb', line 6

def self.snapshot(
    database, username,
    builddir: Prick.state.schema_dir)

  conn = Prick.state.connection
  builder = Prick::Build::Builder.new(conn, builddir)
  pool = builder.pool
  schemas = pool.refresh_schemas

  # Clear snapshots table
  conn.exec "delete from prick.snapshots"

  # Fill it again
  records = []
  for schema in schemas
    conn.schema.list_tables(schema).each { |table|
      max_id = conn.value "select max(id) from #{schema}.#{table}"
      records << { schema_name: schema, table_name: table, max_id: max_id}
    }
  end
  conn.insert("prick", "snapshots", records)

  # Patch fox state file
  state = YAML.load(IO.read(Prick.state.fox_state_file))
  for record in records
    state[:ids][record[:schema_name] + '.' + record[:table_name]] = record[:max_id] if record[:max_id]
  end
  IO.write(Prick.state.fox_state_file, YAML.dump(state))
end

.super_connObject



120
121
# File 'lib/prick-command.rb', line 120

def self.super_conn = Prick.super_conn
#   def self.conn = Prick.conn

.teardown(*databases, remove_state_file: false) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/prick/subcommand/prick-teardown.rb', line 6

def self.teardown(*databases, remove_state_file: false)
  databases = Array(databases).flatten
  owners = databases.map { |d| 
    super_conn.rdbms.exist?(d) ? super_conn.rdbms.owner(d) : d
  }.uniq
  users = databases.map { |d| super_conn.role.list(database: d) }.flatten.uniq

  databases.each { |d| drop_database(d) }
  super_conn.role.drop(users)
  super_conn.role.drop(owners, fail: false)
  owners.each { |o| drop_owner(o) }

  FileUtils.rm_f Prick.state.state_file if remove_state_file

  # TODO Run builder teardown scrips
end

.touch(success) ⇒ Object



6
7
8
# File 'lib/prick/subcommand/prick-touch.rb', line 6

def self.touch(success)
  Prick.state.save_build(success)
end